Webylon 3.2 API Docs
  • Package
  • Class
  • Tree
  • Deprecated
  • Download
Version: current
  • 3.2
  • 3.1

Packages

  • 1c
    • exchange
      • catalog
  • auth
  • Booking
  • building
    • company
  • cart
    • shipping
    • steppedcheckout
  • Catalog
    • monument
  • cms
    • assets
    • batchaction
    • batchactions
    • bulkloading
    • comments
    • content
    • core
    • export
    • newsletter
    • publishers
    • reports
    • security
    • tasks
  • Dashboard
  • DataObjectManager
  • event
  • faq
  • forms
    • actions
    • core
    • fields-basic
    • fields-dataless
    • fields-datetime
    • fields-files
    • fields-formatted
    • fields-formattedinput
    • fields-relational
    • fields-structural
    • transformations
    • validators
  • googlesitemaps
  • guestbook
  • installer
  • newsletter
  • None
  • photo
    • gallery
  • PHP
  • polls
  • recaptcha
  • sapphire
    • api
    • bulkloading
    • control
    • core
    • cron
    • dev
    • email
    • fields-formattedinput
    • filesystem
    • formatters
    • forms
    • i18n
    • integration
    • misc
    • model
    • parsers
    • search
    • security
    • tasks
    • testing
    • tools
    • validation
    • view
    • widgets
  • seo
    • open
      • graph
  • sfDateTimePlugin
  • spamprotection
  • stealth
    • captha
  • subsites
  • userform
    • pagetypes
  • userforms
  • webylon
  • widgets

Classes

  • Archive
  • File
  • Filesystem
  • FlushGeneratedImagesTask
  • FLV
  • Folder
  • Folder_UnusedAssetsField
  • GD
  • Image
  • Image_Cached
  • MediawebPage_Image
  • MP3
  • SaveFileSizeTask
  • TarballArchive
  • Upload
  • Upload_Validator
  • VideoFile
  1 <?php
  2 /**
  3  * Represents a folder in the assets/ directory.
  4  * 
  5  * @package sapphire
  6  * @subpackage filesystem
  7  */
  8 class Folder extends File {
  9     
 10     static $default_sort = "\"Sort\"";
 11     
 12     /*
 13      * Find the given folder or create it, recursively.
 14      * 
 15      * @param $folderPath string Absolute or relative path to the file
 16      */
 17     static function findOrMake($folderPath) {
 18         // Create assets directory, if it is missing
 19         if(!file_exists(ASSETS_PATH)) mkdir(ASSETS_PATH,Filesystem::$folder_create_mask);
 20 
 21         $folderPath = trim(Director::makeRelative($folderPath));
 22         // replace leading and trailing slashes
 23         $folderPath = preg_replace('/^\/?(.*)\/?$/', '$1', $folderPath);
 24 
 25         $parts = explode("/",$folderPath);
 26         if ($parts[0] == ASSETS_DIR) array_shift($parts);
 27 
 28         $parentID = 0;
 29 
 30         foreach($parts as $part) {
 31             if(!$part) continue; // happens for paths with a trailing slash
 32             
 33             $item = DataObject::get_one("Folder", "\"Name\" = '$part' AND \"ParentID\" = $parentID");
 34             if(!$item) {
 35                 $item = new Folder();
 36                 $item->ParentID = $parentID;
 37                 $item->Name = $part;
 38                 $item->Title = $part;
 39                 $item->write();
 40             }
 41             if(!file_exists($item->getFullPath())) {
 42                 mkdir($item->getFullPath(),Filesystem::$folder_create_mask);
 43             }
 44             $parentID = $item->ID;
 45         }
 46         return $item;
 47     }
 48     
 49     /**
 50      * Syncronise the file database with the actual content of the assets folder
 51      */
 52     function syncChildren() {
 53         $parentID = (int)$this->ID; // parentID = 0 on the singleton, used as the 'root node';
 54         $added = 0;
 55         $deleted = 0;
 56 
 57         // First, merge any children that are duplicates
 58         $duplicateChildrenNames = DB::query("SELECT \"Name\" FROM \"File\" WHERE \"ParentID\" = $parentID GROUP BY \"Name\" HAVING count(*) > 1")->column();
 59         if($duplicateChildrenNames) foreach($duplicateChildrenNames as $childName) {
 60             $childName = DB::getConn()->addslashes($childName);
 61             // Note, we do this in the database rather than object-model; otherwise we get all sorts of problems about deleting files
 62             $children = DB::query("SELECT \"ID\" FROM \"File\" WHERE \"Name\" = '$childName' AND \"ParentID\" = $parentID")->column();
 63             if($children) {
 64                 $keptChild = array_shift($children);
 65                 foreach($children as $removedChild) {
 66                     DB::query("UPDATE \"File\" SET \"ParentID\" = $keptChild WHERE \"ParentID\" = $removedChild");
 67                     DB::query("DELETE FROM \"File\" WHERE \"ID\" = $removedChild");
 68                 }
 69             } else {
 70                 user_error("Inconsistent database issue: SELECT ID FROM \"File\" WHERE Name = '$childName' AND ParentID = $parentID should have returned data", E_USER_WARNING);
 71             }
 72         }
 73 
 74         
 75         // Get index of database content
 76         // We don't use DataObject so that things like subsites doesn't muck with this.
 77         $dbChildren = DB::query("SELECT * FROM \"File\" WHERE \"ParentID\" = $parentID");
 78         $hasDbChild = array();
 79         if($dbChildren) {
 80             foreach($dbChildren as $dbChild) {
 81                 $className = $dbChild['ClassName'];
 82                 if(!$className) $className = "File";
 83                 $hasDbChild[$dbChild['Name']] = new $className($dbChild);
 84             }
 85         }
 86         $unwantedDbChildren = $hasDbChild;
 87         
 88         
 89         // Iterate through the actual children, correcting the database as necessary
 90         $baseDir = $this->FullPath;
 91         
 92         if(!$this->Filename) die($this->ID . " - " . $this->FullPath);
 93         
 94 
 95         if(file_exists($baseDir)) {
 96             $actualChildren = scandir($baseDir);
 97             foreach($actualChildren as $actualChild) {
 98                 if($actualChild[0] == '.' || $actualChild[0] == '_' || substr($actualChild,0,6) == 'Thumbs') {
 99                     continue;
100                 }
101 
102                 // A record with a bad class type doesn't deserve to exist. It must be purged!
103                 if(isset($hasDbChild[$actualChild])) {
104                     $child = $hasDbChild[$actualChild];
105                     if(( !( $child instanceof Folder ) && is_dir($baseDir . $actualChild) ) 
106                     || (( $child instanceof Folder ) && !is_dir($baseDir . $actualChild)) ) {
107                         DB::query("DELETE FROM \"File\" WHERE \"ID\" = $child->ID");
108                         unset($hasDbChild[$actualChild]);                       
109                     }
110                 }
111                 
112                 
113                 if(isset($hasDbChild[$actualChild])) {
114                     $child = $hasDbChild[$actualChild];
115                     unset($unwantedDbChildren[$actualChild]);
116                 } else {
117                     $added++;
118                     $childID = $this->constructChild($actualChild);
119                     $child = DataObject::get_by_id("File", $childID);
120                 }
121                     
122                 if( $child && is_dir($baseDir . $actualChild)) {
123                     $childResult = $child->syncChildren();
124                     $added += $childResult['added'];
125                     $deleted += $childResult['deleted'];
126                 }
127                 
128                 // Clean up the child record from memory after use. Important!
129                 $child->destroy();
130                 $child = null;
131             }
132             
133             // Iterate through the unwanted children, removing them all
134             if(isset($unwantedDbChildren)) foreach($unwantedDbChildren as $unwantedDbChild) {
135                 DB::query("DELETE FROM \"File\" WHERE \"ID\" = $unwantedDbChild->ID");
136                 $deleted++;
137             }
138         } else {
139             DB::query("DELETE FROM \"File\" WHERE \"ID\" = $this->ID");
140         }
141         
142         return array('added' => $added, 'deleted' => $deleted);
143     }
144 
145     /**
146      * Construct a child of this Folder with the given name.
147      * It does this without actually using the object model, as this starts messing
148      * with all the data.  Rather, it does a direct database insert.
149      */
150     function constructChild($name) {
151         // Determine the class name - File, Folder or Image
152         $baseDir = $this->FullPath;
153         if(is_dir($baseDir . $name)) {
154             $className = "Folder";
155         } else {
156             // Could use getimagesize to get the type of the image
157             $ext = strtolower(substr($name,strrpos($name,'.')+1));
158             switch($ext) {
159                 case "gif": case "jpg": case "jpeg": case "png": $className = "Image"; break;
160                 default: $className = "File";
161             }
162         }
163 
164         if(Member::currentUser()) $ownerID = Member::currentUser()->ID;
165         else $ownerID = 0;
166         
167         $filename = DB::getConn()->addslashes($this->Filename . $name);
168         if($className == 'Folder' ) $filename .= '/';
169 
170         $name = DB::getConn()->addslashes($name);
171         
172         DB::query("INSERT INTO \"File\" 
173             (\"ClassName\", \"ParentID\", \"OwnerID\", \"Name\", \"Filename\", \"Created\", \"LastEdited\", \"Title\")
174             VALUES ('$className', $this->ID, $ownerID, '$name', '$filename', " . DB::getConn()->now() . ',' . DB::getConn()->now() . ", '$name')");
175             
176         return DB::getGeneratedID("File");
177     }
178 
179     /**
180      * Take a file uploaded via a POST form, and save it inside this folder.
181      */
182     function addUploadToFolder($tmpFile) {
183         if(!is_array($tmpFile)) {
184             user_error("Folder::addUploadToFolder() Not passed an array.  Most likely, the form hasn't got the right enctype", E_USER_ERROR);
185         }
186         if(!isset($tmpFile['size'])) {
187             return;
188         }
189         
190         $base = BASE_PATH;
191         // $parentFolder = Folder::findOrMake("Uploads");
192 
193         // Generate default filename
194         $file = Convert::rus2lat($tmpFile['name']);
195         $file = str_replace(' ', '-',$file);
196         $file = ereg_replace('[^A-Za-z0-9+.-]+','',$file);
197         $file = ereg_replace('-+', '-', $file);
198         $file = ereg_replace('\.+', '.', $file);
199 
200         while($file[0] == '_' || $file[0] == '.') {
201             $file = substr($file, 1);
202         }
203 
204         $file = $this->RelativePath . $file;
205         Filesystem::makeFolder(dirname("$base/$file"));
206         
207         $doubleBarrelledExts = array('.gz', '.bz', '.bz2');
208         
209         $ext = "";
210         if(preg_match('/^(.*)(\.[^.]+)$/', $file, $matches)) {
211             $file = $matches[1];
212             $ext = $matches[2];
213             // Special case for double-barrelled 
214             if(in_array($ext, $doubleBarrelledExts) && preg_match('/^(.*)(\.[^.]+)$/', $file, $matches)) {
215                 $file = $matches[1];
216                 $ext = $matches[2] . $ext;
217             }
218         }
219         $origFile = $file;
220 
221         $i = 1;
222         while(file_exists("$base/$file$ext")) {
223             $i++;
224             $oldFile = $file;
225             
226             if(strrpos($file, '_') !== false) {
227                 $file = ereg_replace('_([^_]+$)', '_' . $i, $file);
228             } else {
229                 $file .= "_$i";
230             }
231             
232             if($oldFile == $file && $i > 2) user_error("Couldn't fix $file$ext with $i", E_USER_ERROR);
233         }
234         
235         //if (move_uploaded_file($tmpFile['tmp_name'], "$base/$file$ext")) {
236                 $success = false;
237                 if(!ini_get("open_basedir")) $success = move_uploaded_file($tmpFile['tmp_name'], "$base/$file$ext");
238                 else $success = rename($tmpFile['tmp_name'], "$base/$file$ext");
239 
240                 if ($success) {
241                                     // Update with the new image
242                                     chmod("$base/$file$ext", Filesystem::$file_create_mask);
243                                     return $this->constructChild(basename($file . $ext));
244 
245         } else {
246             if(!file_exists($tmpFile['tmp_name'])) user_error("Folder::addUploadToFolder: '$tmpFile[tmp_name]' doesn't exist", E_USER_ERROR);
247             else user_error("Folder::addUploadToFolder: Couldn't copy '$tmpFile[tmp_name]' to '$base/$file$ext'", E_USER_ERROR);
248             return false;
249         }
250     }
251     
252     function validate() {
253         return new ValidationResult(true);
254     }
255     
256     //-------------------------------------------------------------------------------------------------
257     // Data Model Definition
258 
259     function getRelativePath() {
260         return parent::getRelativePath() . "/";
261     }
262     
263     
264     function onBeforeDelete() {
265         if($this->ID && ($children = $this->AllChildren())) {
266             foreach($children as $child) {
267                 if(!$this->Filename || !$this->Name || !file_exists($this->getFullPath())) {
268                     $child->setField('Name',null);
269                     $child->Filename = null;
270                 }
271                 $child->delete();
272             }
273         }
274 
275         // Do this after so a folder's contents are removed before we delete the folder.
276         if($this->Filename && $this->Name && file_exists($this->getFullPath())) {
277             $files = glob( $this->getFullPath() . '/*' );
278             
279             if( !$files || ( count( $files ) == 1 && preg_match( '/\/_resampled$/', $files[0] ) ) )
280                 Filesystem::removeFolder( $this->getFullPath() );
281         }
282         parent::onBeforeDelete();
283     }
284     
285     /**
286      * Delete the database record (recursively for folders) without touching the filesystem
287      */
288     function deleteDatabaseOnly() {
289         if($children = $this->myChildren()) {
290             foreach($children as $child) $child->deleteDatabaseOnly();
291         }
292 
293         parent::deleteDatabaseOnly();
294     }
295     
296     public function myChildren() {
297         // Ugly, but functional.
298         $ancestors = ClassInfo::ancestry($this->class);
299         foreach($ancestors as $i => $a) {
300             if(isset($baseClass) && $baseClass === -1) {
301                 $baseClass = $a;
302                 break;
303             }
304             if($a == "DataObject") $baseClass = -1;
305         }
306         
307         $g = DataObject::get($baseClass, "\"ParentID\" = " . $this->ID);
308         return $g;
309     }
310     
311     /**
312      * Returns true if this folder has children
313      */
314     public function hasChildren() {
315         return (bool)DB::query("SELECT COUNT(*) FROM \"File\" WHERE ParentID = "
316             . (int)$this->ID)->value();
317     }
318 
319     /**
320      * Returns true if this folder has children
321      */
322     public function hasChildFolders() {
323         $SQL_folderClasses = Convert::raw2sql(ClassInfo::subclassesFor('Folder'));
324         
325         return (bool)DB::query("SELECT COUNT(*) FROM \"File\" WHERE \"ParentID\" = " . (int)$this->ID
326             . " AND \"ClassName\" IN ('" . implode("','", $SQL_folderClasses) . "')")->value();
327     }
328     
329     /**
330      * Overload autosetFilename() to call autosetFilename() on all the children, too
331      */
332     public function autosetFilename() {
333         parent::autosetFilename();
334 
335         if($this->ID && ($children = $this->AllChildren())) {
336             $this->write();
337 
338             foreach($children as $child) {
339                 $child->autosetFilename();
340                 $child->write();
341             }
342         }
343     }
344 
345     /**
346      * Overload resetFilename() to call resetFilename() on all the children, too.
347      * Pass renamePhysicalFile = false, since the folder renaming will have taken care of this
348      */
349     protected function resetFilename($renamePhysicalFile = true) {
350         parent::resetFilename($renamePhysicalFile);
351 
352         if($this->ID && ($children = $this->AllChildren())) {
353             $this->write();
354 
355             foreach($children as $child) {
356                 $child->resetFilename(false);
357                 $child->write();
358             }
359         }
360     }
361     
362     /**
363      * This isn't a decendant of SiteTree, but needs this in case
364      * the group is "reorganised";
365      */
366     function cmsCleanup_parentChanged(){
367         
368     }
369 
370     /**
371      * Return the FieldSet used to edit this folder in the CMS.
372      * You can modify this fieldset by subclassing folder, or by creating a {@link DataObjectDecorator}
373      * and implemeting updateCMSFields(FieldSet $fields) on that decorator. 
374      */
375     function getCMSFields() {
376         $fileList = new AssetTableField(
377             $this,
378             "Files",
379             "File", 
380             array("Title" => _t('Folder.TITLE', "Title"), "Filename" => _t('Folder.FILENAME', "Filename"), 'Size' => _t('Folder.FILESIZE', 'Size')),
381             ""
382         );
383         $fileList->setFolder($this);
384         $fileList->setPopupCaption(_t('Folder.VIEWEDITASSET', "View/Edit Asset"));
385 
386         $titleField = ($this->ID && $this->ID != "root") ? new TextField("Title", _t('Folder.TITLE')) : new HiddenField("Title");
387         if( $this->canEdit() ) {
388             $deleteButton = new InlineFormAction('deletemarked',_t('Folder.DELSELECTED','Delete selected files'), 'delete');
389             $deleteButton->includeDefaultJS(false);
390         } else {
391             $deleteButton = new HiddenField('deletemarked');
392         }
393 
394         $fields = new FieldSet(
395             new HiddenField("Name"),
396             new TabSet("Root", 
397                 new Tab("Files", _t('Folder.FILESTAB', "Files"),
398                     $titleField,
399                     $fileList,
400                     $deleteButton,
401                     new HiddenField("FileIDs"),
402                     new HiddenField("DestFolderID")
403                 ),
404                 new Tab("Details", _t('Folder.DETAILSTAB', "Details"), 
405                     new ReadonlyField("URL", _t('Folder.URL', 'URL')),
406                     new ReadonlyField("ClassName", _t('Folder.TYPE','Type')),
407                     new ReadonlyField("Created", _t('Folder.CREATED','First Uploaded')),
408                     new ReadonlyField("LastEdited", _t('Folder.LASTEDITED','Last Updated'))
409                 ),
410                 new Tab("Upload", _t('Folder.UPLOADTAB', "Upload"),
411                     new LiteralField("UploadIframe",
412                         $this->getUploadIframe()
413                     )
414                 )
415                 /* // commenting out unused files tab till bugs are fixed
416                 new Tab("UnusedFiles", _t('Folder.UNUSEDFILESTAB', "Unused files"),
417                     new Folder_UnusedAssetsField($this)
418                 ) */
419             ),
420             new HiddenField("ID")
421         );
422         
423         if(!$this->canEdit()) {
424             $fields->removeFieldFromTab("Root", "Upload");
425         }
426 
427         $this->extend('updateCMSFields', $fields);
428         
429         return $fields;
430     }
431     
432     /**
433      * Looks for files used in system and create where clause which contains all ID's of files.
434      * 
435      * @returns String where clause which will work as filter.
436      */
437     public function getUnusedFilesListFilter() {
438         $result = DB::query("SELECT DISTINCT \"FileID\" FROM \"DataObjectFileTracking\"");
439         if($result->numRecords() > 0) {
440             return "\"File\".\"ID\" NOT IN (" . implode(', ', $result->column()) . ") AND (\"ClassName\" = 'File' OR \"ClassName\" = 'Image')";
441         } else {
442             return "(\"ClassName\" = 'File' OR \"ClassName\" = 'Image')";
443         }
444         
445         $usedFiles = array();
446         $where = '';
447         $classes = ClassInfo::subclassesFor('SiteTree');
448         
449         if($result->numRecords() > 0) {
450             while($nextResult = $result->next()) {
451                 $where .= $nextResult['FileID'] . ','; 
452             }
453         }
454 
455         foreach($classes as $className) {
456             $query = singleton($className)->extendedSQL();
457             $ids = $query->execute()->column();
458             if(!count($ids)) continue;
459             
460             foreach(singleton($className)->has_one() as $relName => $joinClass) {
461                 if($joinClass == 'Image' || $joinClass == 'File') {
462                     $fieldName = $relName .'ID';
463                     $query = singleton($className)->extendedSQL("$fieldName > 0");
464                     $query->distinct = true;
465                     $query->select = array($fieldName);
466                     $usedFiles = array_merge($usedFiles, $query->execute()->column());
467 
468                 } elseif($joinClass == 'Folder') {
469                     // @todo
470                 }
471             }
472         }
473         
474         if($usedFiles) {
475             return "\"File\".\"ID\" NOT IN (" . implode(', ', $usedFiles) . ") AND (\"ClassName\" = 'File' OR \"ClassName\" = 'Image')";
476 
477         } else {
478             return "(\"ClassName\" = 'File' OR \"ClassName\" = 'Image')";
479         }
480         return $where;
481     }
482 
483     /**
484      * Display the upload form.  Returns an iframe tag that will show admin/assets/uploadiframe.
485      */
486     function getUploadIframe() {
487         return <<<HTML
488         <iframe name="AssetAdmin_upload" src="admin/assets/uploadiframe/{$this->ID}" id="AssetAdmin_upload" border="0" style="border-style none !important; width: 97%; min-height: 300px; height: 100%; height: expression(document.body.clientHeight) !important;">
489         </iframe>
490 HTML;
491     }
492     
493     /**
494      * Get the children of this folder that are also folders.
495      */
496     function ChildFolders() {
497         return DataObject::get("Folder", "\"ParentID\" = " . (int)$this->ID);
498     }
499 }
500 
501 /**
502  * @package sapphire
503  * @subpackage filesystem
504  */
505 class Folder_UnusedAssetsField extends CompositeField {
506     protected $folder;
507     
508     public function __construct($folder) {
509         $this->folder = $folder;
510         parent::__construct(new FieldSet());
511     }
512         
513     public function getChildren() {
514         if($this->children->Count() == 0) {
515             $inlineFormAction = new InlineFormAction("delete_unused_thumbnails", _t('Folder.DELETEUNUSEDTHUMBNAILS', 'Delete unused thumbnails'));
516             $inlineFormAction->includeDefaultJS(false) ;
517 
518             $this->children = new FieldSet(
519                 new LiteralField( "UnusedAssets", "<h2>"._t('Folder.UNUSEDFILESTITLE', 'Unused files')."</h2>" ),
520                 $this->getAssetList(),
521                 new FieldGroup(
522                     new LiteralField( "UnusedThumbnails", "<h2>"._t('Folder.UNUSEDTHUMBNAILSTITLE', 'Unused thumbnails')."</h2>"),
523                     $inlineFormAction
524                 )
525             );
526             $this->children->setForm($this->form);
527         }
528         return $this->children;
529     }
530     
531     public function FieldHolder() {
532         $output = "";
533         foreach($this->getChildren() as $child) {
534             $output .= $child->FieldHolder();
535         }
536         return $output;
537     }
538 
539 
540     /**
541      * Creates table for displaying unused files.
542      *
543      * @returns AssetTableField
544      */
545     protected function getAssetList() {
546         $where = $this->folder->getUnusedFilesListFilter();
547         $assetList = new AssetTableField(
548             $this->folder,
549             "AssetList",
550             "File", 
551             array("Title" => _t('Folder.TITLE', "Title"), "LinkedURL" => _t('Folder.FILENAME', "Filename"), 'Size' => _t('Folder.FILESIZE', 'Size')), 
552             "",
553             $where
554         );
555         $assetList->setPopupCaption(_t('Folder.VIEWASSET', "View Asset"));
556         $assetList->setPermissions(array("show","delete"));
557         $assetList->Markable = false;
558         return $assetList;
559     }
560 }
561 ?>
562 
[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. -
Webylon 3.2 API Docs API documentation generated by ApiGen 2.8.0