Version: current

      1 <?php
      2 /**
      3  * AssetAdmin is the 'file store' section of the CMS.
      4  * It provides an interface for maniupating the File and Folder objects in the system.
      5  *
      6  * @package cms
      7  * @subpackage assets
      8  */
      9 class AssetAdmin extends LeftAndMain {
     10
     11     static $url_segment = 'assets';
     12
     13     static $url_rule = '/$Action/$ID';
     14
     15     static $menu_title = 'Files & Images';
     16
     17     public static $tree_class = 'File';
     18
     19     /**
     20      * @see Upload->allowedMaxFileSize
     21      * @var int
     22      */
     23     public static $allowed_max_file_size;
     24
     25     static $allowed_actions = array(
     26         'addfolder',
     27         'deletefolder',
     28         'deletemarked',
     29         'DeleteItemsForm',
     30         'deleteUnusedThumbnails',
     31         'doUpload',
     32         'doMultiUpload',
     33         'getfile',
     34         'getsubtree',
     35         'movemarked',
     36         'removefile',
     37         'save',
     38         'savefile',
     39         'sync',
     40         'uploadiframe',
     41         'UploadForm',
     42         'handleupload',
     43         'deleteUnusedThumbnails' => 'ADMIN'
     44     );
     45
     46     /**
     47      * @var boolean Enables upload of additional textual information
     48      * alongside each file (through multifile.js), which makes
     49      * batch changes easier.
     50      *
     51      * CAUTION: This is an unstable API which might change.
     52      */
     53     public static $metadata_upload_enabled = false;
     54
     55     /**
     56      * Return fake-ID "root" if no ID is found (needed to upload files into the root-folder)
     57      */
     58     public function currentPageID() {
     59         if(isset($_REQUEST['ID']) && is_numeric($_REQUEST['ID']))   {
     60             return $_REQUEST['ID'];
     61         } elseif (is_numeric($this->urlParams['ID'])) {
     62             return $this->urlParams['ID'];
     63         } elseif(is_numeric(Session::get("{$this->class}.currentPage"))) {
     64             return Session::get("{$this->class}.currentPage");
     65         } else {
     66             return "root";
     67         }
     68     }
     69
     70     /**
     71      * Set up the controller, in particular, re-sync the File database with the assets folder./
     72      */
     73     function init() {
     74         parent::init();
     75
     76         if(!file_exists(ASSETS_PATH)) {
     77             mkdir(ASSETS_PATH);
     78         }
     79
     80         // needed for MemberTableField (Requirements not determined before Ajax-Call)
     81         Requirements::css(THIRDPARTY_DIR . "/greybox/greybox.css");
     82         Requirements::css(SAPPHIRE_DIR . "/css/ComplexTableField.css");
     83
     84         Requirements::javascript(CMS_DIR . "/javascript/AssetAdmin.js");
     85
     86         Requirements::javascript(THIRDPARTY_DIR . "/greybox/AmiJS.js");
     87         Requirements::javascript(THIRDPARTY_DIR . "/greybox/greybox.js");
     88         Requirements::css(THIRDPARTY_DIR . "/greybox/greybox.css");
     89
     90         Requirements::css(CMS_DIR . "/css/AssetAdmin.css");
     91
     92         Requirements::customScript(<<<JS
     93             _TREE_ICONS = {};
     94             _TREE_ICONS['Folder'] = {
     95                     fileIcon: 'sapphire/javascript/tree/images/page-closedfolder.gif',
     96                     openFolderIcon: 'sapphire/javascript/tree/images/page-openfolder.gif',
     97                     closedFolderIcon: 'sapphire/javascript/tree/images/page-closedfolder.gif'
     98             };
     99 JS
    100         );
    101     }
    102
    103     /**
    104      * Show the content of the upload iframe.  The form is specified by a template.
    105      */
    106     function uploadiframe() {
    107         Requirements::clear();
    108
    109         Requirements::javascript(SAPPHIRE_DIR . "/thirdparty/prototype/prototype.js");
    110         Requirements::javascript(SAPPHIRE_DIR . '/javascript/loader.js');
    111         Requirements::javascript(SAPPHIRE_DIR . "/thirdparty/behaviour/behaviour.js");
    112         Requirements::javascript(SAPPHIRE_DIR . "/javascript/prototype_improvements.js");
    113         Requirements::javascript(SAPPHIRE_DIR . "/javascript/layout_helpers.js");
    114         Requirements::javascript(CMS_DIR . "/javascript/LeftAndMain.js");
    115 //      Requirements::javascript(CMS_DIR . "/thirdparty/multifile/multifile.js");
    116 //      Requirements::css(CMS_DIR . "/thirdparty/multifile/multifile.css");
    117         Requirements::javascript(SAPPHIRE_DIR . "/thirdparty/jquery/jquery.js");
    118         Requirements::javascript(SAPPHIRE_DIR . "/javascript/jquery_improvements.js");
    119         Requirements::css(CMS_DIR . "/css/typography.css");
    120         Requirements::css(CMS_DIR . "/css/layout.css");
    121         Requirements::css(CMS_DIR . "/css/cms_left.css");
    122         Requirements::css(CMS_DIR . "/css/cms_right.css");
    123
    124         if(isset($data['ID']) && $data['ID'] != 'root') $folder = DataObject::get_by_id("Folder", $data['ID']);
    125         else $folder = singleton('Folder');
    126
    127         return array( 'CanUpload' => $folder->canEdit());
    128     }
    129
    130     /**
    131      * Needs to be enabled through {@link AssetAdmin::$metadata_upload_enabled}
    132      *
    133      * @return String
    134      */
    135     function UploadMetadataHtml() {
    136         if(!self::$metadata_upload_enabled) return;
    137
    138         $fields = singleton('File')->uploadMetadataFields();
    139
    140         // Return HTML with markers for easy replacement
    141         $fieldHtml = '';
    142         foreach($fields as $field) $fieldHtml = $fieldHtml . $field->FieldHolder();
    143         $fieldHtml = preg_replace('/(name|for|id)="(.+?)"/', '$1="$2[__X__]"', $fieldHtml);
    144
    145         // Icky hax to fix certain elements with fixed ids
    146         $fieldHtml = preg_replace('/-([a-zA-Z0-9]+?)\[__X__\]/', '[__X__]-$1', $fieldHtml);
    147
    148         return $fieldHtml;
    149     }
    150
    151     /**
    152      * Return the form object shown in the uploadiframe.
    153      */
    154     function UploadForm() {
    155         $ff = new MultiUploadField(
    156             'UploadForm',
    157             'Upload',
    158             "",
    159             array(
    160                     'file_upload_limit' => 10, // how many files can be uploaded
    161                     'file_queue_limit' => 10, // how many files can be in the queue at once
    162                     'upload_url' => Director::absoluteURL('admin/assets/handleupload'),
    163                     'required' => 'false'
    164             )
    165         );
    166
    167         MultiUploadConfig::addStaticPostParams(array(
    168             'FolderID' => $this->currentPageID(),
    169         ));
    170         MultiUploadConfig::addFileTypes(File::$allowed_extensions);
    171
    172         if (property_exists('WebylonSiteConfig', 'max_file_upload_size')) {
    173             MultiUploadConfig::$file_size_limit = WebylonSiteConfig::$max_file_upload_size;
    174         } else {
    175             MultiUploadConfig::$file_size_limit = ((int)ini_get('upload_max_filesize')) * 1024 * 1024;
    176         }
    177
    178         $form = new Form($this,'UploadForm', new FieldSet(
    179             new HiddenField("ID", "", $this->currentPageID()),
    180             new HiddenField("FolderID", "", $this->currentPageID()),
    181             new HiddenField("action_doMultiUpload", "", "1"),
    182             $ff
    183         ), new FieldSet(
    184         ));
    185
    186         $this->extend('updateUploadForm', $form);
    187
    188         // Makes ajax easier
    189         $form->disableSecurityToken();
    190
    191         return $form;
    192     }
    193
    194     /**
    195      * This method processes the results of the UploadForm. !!! NOT USED HERE, BUT USED IN CMSMAIN IN Visual Editor !!!
    196      * It will save the uploaded files to /assets/ and create new File objects as required.
    197      */
    198     function doUpload($data, $form) {
    199         $newFiles = array();
    200         $fileIDs = array();
    201         $fileNames = array();
    202         $fileSizeWarnings = '';
    203         $uploadErrors = '';
    204         $jsErrors = '';
    205         $status = '';
    206         $statusMessage = '';
    207         $processedFiles = array();
    208
    209         foreach($data['Files'] as $param => $files) {
    210             if(!is_array($files)) $files = array($files);
    211             foreach($files as $key => $value) {
    212                 $processedFiles[$key][$param] = $value;
    213             }
    214         }
    215
    216         // Load POST data from arrays in to the correct dohickey.
    217         $processedData = array();
    218         foreach($data as $dataKey => $value) {
    219             if ($dataKey == 'Files') continue;
    220             if (is_array($value)) {
    221                 $i = 0;
    222                 foreach($value as $fileId => $dataValue) {
    223                     if (!isset($processedData[$i])) $processedData[$i] = array();
    224                     $processedData[$i][$dataKey] = $dataValue;
    225                     $i++;
    226                 }
    227             }
    228         }
    229         $processedData = array_reverse($processedData);
    230
    231         if($data['FolderID'] && $data['FolderID'] != '') $folder = DataObject::get_by_id("Folder", $data['FolderID']);
    232         else $folder = singleton('Folder');
    233
    234         foreach($processedFiles as $filePostId => $tmpFile) {
    235             if($tmpFile['error'] == UPLOAD_ERR_NO_TMP_DIR) {
    236                 $status = 'bad';
    237                 $statusMessage = _t('AssetAdmin.NOTEMP', 'There is no temporary folder for uploads. Please set upload_tmp_dir in php.ini.');
    238                 break;
    239             }
    240
    241             if($tmpFile['tmp_name']) {
    242                 // Workaround open_basedir problems
    243                 if(ini_get("open_basedir")) {
    244                     $newtmp = TEMP_FOLDER . '/' . $tmpFile['name'];
    245                     move_uploaded_file($tmpFile['tmp_name'], $newtmp);
    246                     $tmpFile['tmp_name'] = $newtmp;
    247                 }
    248
    249                 // validate files (only if not logged in as admin)
    250                 if(!File::$apply_restrictions_to_admin && (Permission::check('ADMIN') || Permission::check('CMS_ACCESS_AssetAdmin'))) {
    251                     $valid = true;
    252                 } else {
    253
    254                     // Set up the validator instance with rules
    255                     $validator = new Upload_Validator();
    256                     $validator->setAllowedExtensions(File::$allowed_extensions);
    257                     $validator->setAllowedMaxFileSize(self::$allowed_max_file_size);
    258
    259                     // Do the upload validation with the rules
    260                     $upload = new Upload();
    261                     $upload->setValidator($validator);
    262                     $valid = $upload->validate($tmpFile);
    263                     if(!$valid) {
    264                         $errors = $upload->getErrors();
    265                         if($errors) foreach($errors as $error) {
    266                             $jsErrors .= "alert('" . Convert::raw2js($error) . "');";
    267                         }
    268                     }
    269                 }
    270
    271                 // move file to given folder
    272                 if($valid && $folder) {
    273                     $newFile = $folder->addUploadToFolder($tmpFile);
    274
    275                     if(self::$metadata_upload_enabled && isset($processedData[$filePostId])) {
    276                         $fileObject = DataObject::get_by_id('File', $newFile);
    277                         $metadataForm = new Form($this, 'MetadataForm', $fileObject->uploadMetadataFields(), new FieldSet());
    278                         $metadataForm->loadDataFrom($processedData[$filePostId]);
    279                         $metadataForm->saveInto($fileObject);
    280                         $fileObject->write();
    281                     }
    282
    283                     $newFiles[] = $newFile;
    284                 }
    285             }
    286         }
    287
    288         if($newFiles) {
    289             $numFiles = sizeof($newFiles);
    290             $statusMessage = sprintf(_t('AssetAdmin.UPLOADEDX',"Uploaded %s files"),$numFiles);
    291             $status = "good";
    292         } else if($status != 'bad') {
    293             $statusMessage = _t('AssetAdmin.NOTHINGTOUPLOAD','There was nothing to upload');
    294             $status = "";
    295         }
    296
    297         $fileObj = false;
    298         foreach($newFiles as $newFile) {
    299             $fileIDs[] = $newFile;
    300             $fileObj = DataObject::get_one('File', "\"File\".\"ID\"=$newFile");
    301             // notify file object after uploading
    302             if (method_exists($fileObj, 'onAfterUpload')) $fileObj->onAfterUpload();
    303             $fileNames[] = $fileObj->Name;
    304         }
    305
    306         // workaround for content editors image upload.Passing an extra hidden field
    307         // in the content editors view of 'UploadMode' @see HtmlEditorField
    308         // this will be refactored for 2.5
    309         if(isset($data['UploadMode']) && $data['UploadMode'] == "CMSEditor" && $fileObj) {
    310             // we can use $fileObj considering that the uploader in the cmseditor can only upload
    311             // one file at a time. Once refactored to multiple files this is going to have to be changed
    312             $width = (is_a($fileObj, 'Image')) ? $fileObj->getWidth() : '100';
    313             $height = (is_a($fileObj, 'Image')) ? $fileObj->getHeight() : '100';
    314
    315             $values = array(
    316                 'Filename' => $fileObj->Filename,
    317                 'Width' => $width,
    318                 'Height' => $height
    319             );
    320
    321             return Convert::raw2json($values);
    322         }
    323
    324         $sFileIDs = implode(',', $fileIDs);
    325         $sFileNames = implode(',', $fileNames);
    326
    327         echo <<<HTML
    328             <script type="text/javascript">
    329             /* IDs: $sFileIDs */
    330             /* Names: $sFileNames */
    331
    332             var form = parent.document.getElementById('Form_EditForm');
    333             form.getPageFromServer(form.elements.ID.value);
    334             parent.statusMessage("{$statusMessage}","{$status}");
    335             $jsErrors
    336             parent.document.getElementById('sitetree').getTreeNodeByIdx( "{$folder->ID}" ).getElementsByTagName('a')[0].className += ' contents';
    337             </script>
    338 HTML;
    339     }
    340
    341     public function handleupload() {
    342         $rs = false;
    343         $message = '';
    344         if(isset($_FILES['upload_file']) && !empty($_FILES['upload_file'])) {
    345             $file = new File();
    346             if(!isset($_POST['FolderID']) || !($folder = DataObject::get_by_id("Folder",$_POST['FolderID']))) {
    347                 $folder = singleton('Folder');
    348             }
    349             try {
    350                 if(!class_exists("Upload")) {
    351                     throw new ValidationException(_t("FileDataObjectManager.NoUploadClass", "No Upload class!"), E_USER_WARNING);
    352                 }
    353                 $u = new Upload();
    354                 $u->validator->setAllowedExtensions(File::$allowed_extensions);
    355                 $valid = $u->validate($_FILES['upload_file']);
    356                 if(!$valid) {
    357                     $errors = implode("; ", $u->getErrors());
    358                     throw new ValidationException($errors, E_USER_WARNING);
    359                 }
    360                 $newFile = $folder->addUploadToFolder($_FILES['upload_file']);
    361                 $file = DataObject::get_by_id('File', $newFile);
    362                 if (!$file) {
    363                     $errors = _t("FileDataObjectManager.UploadError", "Upload Error");
    364                     throw new ValidationException($errors, E_USER_WARNING);
    365                 }
    366
    367                 if(isset($_POST['FolderID']))
    368                     $file->setField("ParentID",$folder->ID);
    369
    370                 // Provide an "upgrade" to File subclasses
    371                 if($file->class == "File") {
    372                     $ext = strtolower($file->Extension);
    373                     if(in_array($ext, MP3::$allowed_file_types))
    374                         $file = $file->newClassInstance("MP3");
    375                     else if(in_array($ext, array('jpg','jpeg','gif','png')))
    376                         $file = $file->newClassInstance("Image");
    377                     else if(in_array($ext, FLV::$allowed_file_types))
    378                         $file = $file->newClassInstance("FLV");
    379                 }
    380                 $file->OwnerID = Member::currentUserID();
    381                 $file->write();
    382                 $rs = true;
    383                 $message = $file->ID;
    384             } catch (Exception $e) {
    385                 $rs = false;
    386                 if (method_exists($e,'getResult') && $e->getResult()) {
    387                     $message = $e->getResult()->message();
    388                 } else {
    389                     $message = $e->getMessage();
    390                 }
    391             }
    392         }
    393         echo json_encode(array('rs' => $rs, 'message' => $message));
    394     }
    395
    396     /**
    397      * This method processes the results of the UploadForm.
    398      * It will save the uploaded files to /assets/ and create new File objects as required.
    399      */
    400     function doMultiUpload($data, $form) {
    401         if(isset($data['uploaded_files']) && !empty($data['uploaded_files'])) {
    402             $statusMessage = sprintf(_t('AssetAdmin.UPLOADEDX',"Uploaded %s files"), count($data['uploaded_files']));
    403             $status = "good";
    404         } else {
    405             $statusMessage = _t('AssetAdmin.NOTHINGTOUPLOAD','There was nothing to upload');
    406             $status = "";
    407         }
    408
    409         $folderID = 0;
    410         if (isset($data['FolderID'])) {
    411             $folderID = $data['FolderID'];
    412         }
    413
    414         echo <<<HTML
    415             <script type="text/javascript">
    416
    417             var form = parent.document.getElementById('Form_EditForm');
    418             form.getPageFromServer(form.elements.ID.value);
    419             parent.statusMessage("{$statusMessage}","{$status}");
    420             parent.document.getElementById('sitetree').getTreeNodeByIdx( "{$folderID}" ).getElementsByTagName('a')[0].className += ' contents';
    421             </script>
    422 HTML;
    423     }
    424
    425
    426     /**
    427      * Custom currentPage() method to handle opening the 'root' folder
    428      */
    429     public function currentPage() {
    430         $id = $this->currentPageID();
    431         if($id && is_numeric($id)) {
    432             return DataObject::get_by_id($this->stat('tree_class'), $id);
    433         } else if($id == 'root') {
    434             return singleton($this->stat('tree_class'));
    435         }
    436     }
    437
    438     /**
    439      * Return the form that displays the details of a folder, including a file list and fields for editing the folder name.
    440      */
    441     function getEditForm($id) {
    442         if($id && $id != "root") {
    443             $record = DataObject::get_by_id("File", $id);
    444         } else {
    445             $record = singleton("Folder");
    446         }
    447
    448         if($record) {
    449             $fields = $record->getCMSFields();
    450             $actions = new FieldSet();
    451
    452             // Only show save button if not 'assets' folder
    453             if($record->canEdit() && $id != 'root') {
    454                 $actions = new FieldSet(
    455                     new FormAction('save',_t('AssetAdmin.SAVEFOLDERNAME','Save folder name'))
    456                 );
    457             }
    458
    459             $form = new Form($this, "EditForm", $fields, $actions);
    460             if($record->ID) {
    461                 $form->loadDataFrom($record);
    462             } else {
    463                 $form->loadDataFrom(array(
    464                     "ID" => "root",
    465                     "URL" => Director::absoluteBaseURL() . 'assets/',
    466                 ));
    467             }
    468
    469             if(!$record->canEdit()) {
    470                 $form->makeReadonly();
    471             }
    472
    473             $this->extend('updateEditForm', $form);
    474
    475             return $form;
    476         }
    477     }
    478
    479     /**
    480      * Perform the "move marked" action.
    481      * Called and returns in same way as 'save' function
    482      */
    483     public function movemarked($urlParams, $form) {
    484         if($_REQUEST['DestFolderID'] && (is_numeric($_REQUEST['DestFolderID']) || ($_REQUEST['DestFolderID']) == 'root')) {
    485             $destFolderID = ($_REQUEST['DestFolderID'] == 'root') ? 0 : $_REQUEST['DestFolderID'];
    486             $fileList = "'" . preg_replace('! *, *!',"','",trim(addslashes($_REQUEST['FileIDs']))) . "'";
    487             $numFiles = 0;
    488
    489             if($fileList != "''") {
    490                 $files = DataObject::get("File", "\"File\".\"ID\" IN ($fileList)");
    491                 if($files) {
    492                     foreach($files as $file) {
    493                         if($file instanceof Image) {
    494                             $file->deleteFormattedImages();
    495                         }
    496                         $file->ParentID = $destFolderID;
    497                         $file->write();
    498                         $numFiles++;
    499                     }
    500                 } else {
    501                     user_error("No files in $fileList could be found!", E_USER_ERROR);
    502                 }
    503             }
    504
    505             $message = sprintf(_t('AssetAdmin.MOVEDX','Moved %s files'),$numFiles);
    506
    507             FormResponse::status_message($message, "good");
    508             FormResponse::add("$('Form_EditForm_Files').refresh();");
    509
    510             return FormResponse::respond();
    511         }
    512     }
    513
    514     /**
    515      * Perform the "delete marked" action.
    516      * Called and returns in same way as 'save' function
    517      */
    518     public function deletemarked($urlParams, $form) {
    519         $fileList = "'" . preg_replace('! *, *!',"','",trim(addslashes($_REQUEST['FileIDs']))) . "'";
    520         $numFiles = 0;
    521         $folderID = 0;
    522         $deleteList = '';
    523         $brokenPageList = '';
    524
    525         if($fileList != "''") {
    526             $files = DataObject::get("File", "\"File\".\"ID\" IN ($fileList)");
    527             if($files) {
    528                 foreach($files as $file) {
    529                     if($file instanceof Image) {
    530                         $file->deleteFormattedImages();
    531                     }
    532                     if(!$folderID) {
    533                         $folderID = $file->ParentID;
    534                     }
    535                     $file->delete();
    536                     $numFiles++;
    537                 }
    538                 if($brokenPages = Notifications::getItems('BrokenLink')) {
    539                     $brokenPageList = "  ". _t('AssetAdmin.NOWBROKEN', 'These pages now have broken links:') . '</ul>';
    540                     foreach($brokenPages as $brokenPage) {
    541                         $brokenPageList .= "<li style=&quot;font-size: 65%&quot;>" . $brokenPage->Breadcrumbs(3, true) . '</li>';
    542                     }
    543                     $brokenPageList .= '</ul>';
    544                     Notifications::notifyByEmail("BrokenLink", "Page_BrokenLinkEmail");
    545                 } else {
    546                     $brokenPageList = '';
    547                 }
    548
    549                 $deleteList = '';
    550                 if($folderID) {
    551                     $remaining = DB::query("SELECT COUNT(*) FROM \"File\" WHERE \"ParentID\" = $folderID")->value();
    552                     if(!$remaining) $deleteList .= "Element.removeClassName(\$('sitetree').getTreeNodeByIdx('$folderID').getElementsByTagName('a')[0],'contents');";
    553                 }
    554             } else {
    555                 user_error("No files in $fileList could be found!", E_USER_ERROR);
    556             }
    557         }
    558
    559         $message = sprintf(_t('AssetAdmin.DELETEDX',"Deleted %s files.%s"),$numFiles,$brokenPageList) ;
    560
    561         FormResponse::add($deleteList);
    562         FormResponse::status_message($message, "good");
    563         FormResponse::add("$('Form_EditForm').getPageFromServer($('Form_EditForm_ID').value)");
    564
    565         return FormResponse::respond();
    566     }
    567
    568
    569     /**
    570      * Returns the content to be placed in Form_SubForm when editing a file.
    571      * Called using ajax.
    572      */
    573     public function getfile() {
    574         SSViewer::setOption('rewriteHashlinks', false);
    575
    576         // bdc: only try to return something if user clicked on an object
    577         if (is_object($this->getSubForm($this->urlParams['ID']))) {
    578             return $this->getSubForm($this->urlParams['ID'])->formHtmlContent();
    579         }
    580         else return null;
    581     }
    582
    583     /**
    584      * Action handler for the save button on the file subform.
    585      * Saves the file
    586      */
    587     public function savefile($data, $form) {
    588         $record = DataObject::get_by_id("File", $data['ID']);
    589         $form->saveInto($record);
    590         $record->write();
    591         $title = Convert::raw2js($record->Title);
    592         $name = Convert::raw2js($record->Name);
    593         $saved = sprintf(_t('AssetAdmin.SAVEDFILE','Saved file %s'),"#$data[ID]");
    594         echo <<<JS
    595             statusMessage('$saved');
    596             $('record-$data[ID]').getElementsByTagName('td')[1].innerHTML = "$title";
    597             $('record-$data[ID]').getElementsByTagName('td')[2].innerHTML = "$name";
    598 JS;
    599     }
    600
    601     public function sync() {
    602         echo Filesystem::sync();
    603     }
    604
    605     /**
    606      * Return the entire site tree as a nested UL.
    607      * @return string HTML for site tree
    608      */
    609     public function SiteTreeAsUL() {
    610         $obj = singleton('Folder');
    611         $obj->setMarkingFilter('ClassName', ClassInfo::subclassesFor('Folder'));
    612         $obj->markPartialTree(30, null, "ChildFolders");
    613
    614         if($p = $this->currentPage()) $obj->markToExpose($p);
    615
    616         // getChildrenAsUL is a flexible and complex way of traversing the tree
    617         $siteTreeList = $obj->getChildrenAsUL(
    618             '',
    619             '"<li id=\"record-$child->ID\" class=\"$child->class" . $child->markingClasses() .  ($extraArg->isCurrentPage($child) ? " current" : "") . "\">" . ' .
    620             '"<a href=\"" . Controller::join_links(substr($extraArg->Link(),0,-1), "show", $child->ID) . "\" class=\"" . ($child->hasChildFolders() ? " contents" : "") . "\" >" . $child->TreeTitle() . "</a>" ',
    621             $this,
    622             true,
    623             "ChildFolders"
    624         );
    625
    626         // Wrap the root if needs be
    627         $rootLink = $this->Link() . 'show/root';
    628         $baseUrl = Director::absoluteBaseURL() . "assets";
    629         if(!isset($rootID)) {
    630             $siteTree = "<ul id=\"sitetree\" class=\"tree unformatted\"><li id=\"record-root\" class=\"Root\"><a href=\"$rootLink\"><strong>{$baseUrl}</strong></a>"
    631             . $siteTreeList . "</li></ul>";
    632         }
    633
    634         return $siteTree;
    635     }
    636
    637     /**
    638      * Returns a subtree of items underneat the given folder.
    639      */
    640     public function getsubtree($request) {
    641         $obj = DataObject::get_by_id('Folder', $_REQUEST['ID']);
    642         $obj->setMarkingFilter('ClassName', ClassInfo::subclassesFor('Folder'));
    643         $obj->markPartialTree();
    644
    645         $results = $obj->getChildrenAsUL(
    646             '',
    647             '"<li id=\"record-$child->ID\" class=\"$child->class" . $child->markingClasses() .  ($extraArg->isCurrentPage($child) ? " current" : "") . "\">" . ' .
    648             '"<a href=\"" . Controller::join_links(substr($extraArg->Link(),0,-1), "show", $child->ID) . "\" >" . $child->TreeTitle() . "</a>" ',
    649             $this,
    650             true
    651         );
    652
    653         return substr(trim($results), 4, -5);
    654     }
    655
    656
    657     //------------------------------------------------------------------------------------------//
    658
    659     // Data saving handlers
    660
    661     /**
    662      * Add a new folder and return its details suitable for ajax.
    663      */
    664     public function addfolder() {
    665         $parent = ($_REQUEST['ParentID'] && is_numeric($_REQUEST['ParentID'])) ? (int)$_REQUEST['ParentID'] : 0;
    666         $name = (isset($_REQUEST['Name'])) ? basename($_REQUEST['Name']) : _t('AssetAdmin.NEWFOLDER',"NewFolder");
    667
    668         if($parent) {
    669             $parentObj = DataObject::get_by_id('File', $parent);
    670             if(!$parentObj || !$parentObj->ID) $parent = 0;
    671         }
    672
    673         // Get the folder to be created
    674         if(isset($parentObj->ID)) $filename = $parentObj->FullPath . $name;
    675         else $filename = ASSETS_PATH . '/' . $name;
    676
    677         // Actually create
    678         if(!file_exists(ASSETS_PATH)) {
    679             mkdir(ASSETS_PATH);
    680         }
    681
    682         $p = new Folder();
    683         $p->ParentID = $parent;
    684         $p->Name = $p->Title = basename($filename);
    685
    686         // Ensure uniqueness
    687         $i = 2;
    688         $baseFilename = substr($p->Filename, 0, -1) . '-';
    689         while(file_exists($p->FullPath)) {
    690             $p->Filename = $baseFilename . $i . '/';
    691             $i++;
    692         }
    693
    694         $p->write();
    695
    696         mkdir($p->FullPath);
    697         chmod($p->FullPath, Filesystem::$file_create_mask);
    698
    699         if(isset($_REQUEST['returnID'])) {
    700             return $p->ID;
    701         } else {
    702             return $this->returnItemToUser($p);
    703         }
    704
    705     }
    706
    707     /**
    708      * @return Form
    709      */
    710     function DeleteItemsForm() {
    711         $form = new Form(
    712             $this,
    713             'DeleteItemsForm',
    714             new FieldSet(
    715                 new LiteralField('SelectedPagesNote',
    716                     sprintf('<p>%s</p>', _t('AssetAdmin_left.ss.SELECTTODEL','Select the folders that you want to delete and then click the button below'))
    717                 ),
    718                 new HiddenField('csvIDs')
    719             ),
    720             new FieldSet(
    721                 new FormAction('deletefolder', _t('AssetAdmin_left.ss.DELFOLDERS','Delete the selected folders'))
    722             )
    723         );
    724         $form->addExtraClass('actionparams');
    725
    726         return $form;
    727     }
    728
    729     /**
    730      * Delete a folder
    731      */
    732     public function deletefolder() {
    733         $ids = preg_split('! *, *!', $_REQUEST['csvIDs']);
    734
    735         if(!$ids) return false;
    736         $script = '';
    737
    738         foreach($ids as $id) {
    739             if(is_numeric($id)) {
    740                 $record = DataObject::get_by_id($this->stat('tree_class'), $id);
    741                 if($record) {
    742                     $script .= $this->deleteTreeNodeJS($record);
    743                     $record->delete();
    744                     $record->destroy();
    745                 }
    746             }
    747         }
    748
    749         $size = sizeof($ids);
    750         if($size > 1) {
    751           $message = $size.' '._t('AssetAdmin.FOLDERSDELETED', 'folders deleted.');
    752         } else {
    753           $message = $size.' '._t('AssetAdmin.FOLDERDELETED', 'folder deleted.');
    754         }
    755
    756         $script .= "statusMessage('$message');";
    757
    758         return $script;
    759     }
    760
    761     public function removefile(){
    762         if($fileID = $this->urlParams['ID']) {
    763             $file = DataObject::get_by_id('File', $fileID);
    764             // Delete the temp verions of this file in assets/_resampled
    765             if($file instanceof Image) {
    766                 $file->deleteFormattedImages();
    767             }
    768             $file->delete();
    769             //$file->destroy();
    770
    771             if(Director::is_ajax()) {
    772                 echo <<<JS
    773                 $('Form_EditForm_Files').removeFile($fileID);
    774                 statusMessage('removed file', 'good');
    775 JS;
    776             } else {
    777                 Director::redirectBack();
    778             }
    779         } else {
    780             user_error("AssetAdmin::removefile: Bad parameters: File=$fileID", E_USER_ERROR);
    781         }
    782     }
    783
    784     public function save($urlParams, $form) {
    785         // Don't save the root folder - there's no database record
    786         if($_REQUEST['ID'] == 'root') {
    787             FormResponse::status_message('Saved', 'good');
    788             return FormResponse::respond();
    789         }
    790
    791         $form->dataFieldByName('Name')->Value = $form->dataFieldByName('Title')->Value();
    792
    793         return parent::save($urlParams, $form);
    794     }
    795
    796     /**
    797      * #################################
    798      *        Garbage collection.
    799      * #################################
    800     */
    801
    802     /**
    803      * Removes all unused thumbnails from the file store
    804      * and returns the status of the process to the user.
    805      */
    806     public function deleteunusedthumbnails() {
    807         $count = 0;
    808         $thumbnails = $this->getUnusedThumbnails();
    809
    810         if($thumbnails) {
    811             foreach($thumbnails as $thumbnail) {
    812                 unlink(ASSETS_PATH . "/" . $thumbnail);
    813                 $count++;
    814             }
    815         }
    816
    817         $message = sprintf(_t('AssetAdmin.THUMBSDELETED', '%s unused thumbnails have been deleted'), $count);
    818         FormResponse::status_message($message, 'good');
    819         echo FormResponse::respond();
    820     }
    821
    822     /**
    823      * Creates array containg all unused thumbnails.
    824      *
    825      * Array is created in three steps:
    826      *     1. Scan assets folder and retrieve all thumbnails
    827      *     2. Scan all HTMLField in system and retrieve thumbnails from them.
    828      *     3. Count difference between two sets (array_diff)
    829      *
    830      * @return array
    831      */
    832     private function getUnusedThumbnails() {
    833         $allThumbnails = array();
    834         $usedThumbnails = array();
    835         $dirIterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(ASSETS_PATH));
    836         $classes = ClassInfo::subclassesFor('SiteTree');
    837
    838         if($dirIterator) {
    839             foreach($dirIterator as $file) {
    840                 if($file->isFile()) {
    841                     if(strpos($file->getPathname(), '_resampled') !== false) {
    842                         $pathInfo = pathinfo($file->getPathname());
    843                         if(in_array(strtolower($pathInfo['extension']), array('jpeg', 'jpg', 'jpe', 'png', 'gif'))) {
    844                             $path = str_replace('\\','/', $file->getPathname());
    845                             $allThumbnails[] = substr($path, strpos($path, '/assets/') + 8);
    846                         }
    847                     }
    848                 }
    849             }
    850         }
    851
    852         if($classes) {
    853             foreach($classes as $className) {
    854                 $SNG_class = singleton($className);
    855                 $objects = DataObject::get($className);
    856
    857                 if($objects !== NULL) {
    858                     foreach($objects as $object) {
    859                         foreach($SNG_class->db() as $fieldName => $fieldType) {
    860                             if($fieldType == 'HTMLText') {
    861                                 $url1 = HTTP::findByTagAndAttribute($object->$fieldName,array('img' => 'src'));
    862
    863                                 if($url1 != NULL) {
    864                                     $usedThumbnails[] = substr($url1[0], strpos($url1[0], '/assets/') + 8);
    865                                 }
    866
    867                                 if($object->latestPublished > 0) {
    868                                     $object = Versioned::get_latest_version($className, $object->ID);
    869                                     $url2 = HTTP::findByTagAndAttribute($object->$fieldName, array('img' => 'src'));
    870
    871                                     if($url2 != NULL) {
    872                                         $usedThumbnails[] = substr($url2[0], strpos($url2[0], '/assets/') + 8);
    873                                     }
    874                                 }
    875                             }
    876                         }
    877                     }
    878                 }
    879             }
    880         }
    881
    882         return array_diff($allThumbnails, $usedThumbnails);
    883     }
    884
    885 }
    886 ?>
    887

[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. -