silverstripe-reports/code/AssetAdmin.php
Sam Minnee f8f18f40aa Several merges from branches/2.4
git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/cms/trunk@111631 467b73ca-7a2a-4603-9d3b-597d59a354a9
2010-10-04 06:04:21 +00:00

459 lines
14 KiB
PHP
Executable File
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* AssetAdmin is the 'file store' section of the CMS.
* It provides an interface for maniupating the File and Folder objects in the system.
*
* @package cms
* @subpackage assets
*/
class AssetAdmin extends LeftAndMain {
static $url_segment = 'assets';
static $url_rule = '/$Action/$ID';
static $menu_title = 'Files & Images';
public static $tree_class = 'Folder';
/**
* @see Upload->allowedMaxFileSize
* @var int
*/
public static $allowed_max_file_size;
static $allowed_actions = array(
'deleteUnusedThumbnails',
'doUpload',
'getsubtree',
'save',
'uploadiframe',
'UploadForm',
'deleteUnusedThumbnails' => 'ADMIN',
'batchactions',
'BatchActionsForm'
);
/**
* Return fake-ID "root" if no ID is found (needed to upload files into the root-folder)
*/
public function currentPageID() {
if(isset($_REQUEST['ID']) && is_numeric($_REQUEST['ID'])) {
return $_REQUEST['ID'];
} elseif (is_numeric($this->urlParams['ID'])) {
return $this->urlParams['ID'];
} elseif(is_numeric(Session::get("{$this->class}.currentPage"))) {
return Session::get("{$this->class}.currentPage");
} else {
return "root";
}
}
/**
* Set up the controller, in particular, re-sync the File database with the assets folder./
*/
function init() {
parent::init();
// Create base folder if it doesnt exist already
if(!file_exists(ASSETS_PATH)) Filesystem::makeFolder(ASSETS_PATH);
Requirements::javascript(CMS_DIR . "/javascript/AssetAdmin.js");
Requirements::css(CMS_DIR . "/css/AssetAdmin.css");
Requirements::customScript(<<<JS
_TREE_ICONS = {};
_TREE_ICONS['Folder'] = {
fileIcon: 'sapphire/javascript/tree/images/page-closedfolder.gif',
openFolderIcon: 'sapphire/javascript/tree/images/page-openfolder.gif',
closedFolderIcon: 'sapphire/javascript/tree/images/page-closedfolder.gif'
};
JS
);
CMSBatchActionHandler::register('delete', 'AssetAdmin_DeleteBatchAction', 'Folder');
}
/**
* Show the content of the upload iframe. The form is specified by a template.
*/
function uploadiframe() {
Requirements::clear();
Requirements::javascript(SAPPHIRE_DIR . "/thirdparty/prototype/prototype.js");
Requirements::javascript(SAPPHIRE_DIR . "/thirdparty/behaviour/behaviour.js");
//Requirements::javascript(CMS_DIR . "/javascript/LeftAndMain.js");
Requirements::javascript(CMS_DIR . "/thirdparty/multifile/multifile.js");
Requirements::css(CMS_DIR . "/thirdparty/multifile/multifile.css");
Requirements::javascript(SAPPHIRE_DIR . "/thirdparty/jquery/jquery.js");
Requirements::javascript(SAPPHIRE_DIR . "/javascript/jquery_improvements.js");
Requirements::css(CMS_DIR . "/css/typography.css");
Requirements::css(CMS_DIR . "/css/layout.css");
Requirements::css(CMS_DIR . "/css/cms_left.css");
Requirements::css(CMS_DIR . "/css/cms_right.css");
if(isset($data['ID']) && $data['ID'] != 'root') $folder = DataObject::get_by_id("Folder", $data['ID']);
else $folder = singleton('Folder');
return array( 'CanUpload' => $folder->canEdit());
}
/**
* @return String
*/
function UploadMetadataHtml() {
$fields = singleton('File')->uploadMetadataFields();
// Return HTML with markers for easy replacement
$fieldHtml = '';
foreach($fields as $field) $fieldHtml = $fieldHtml . $field->FieldHolder();
$fieldHtml = preg_replace('/(name|for|id)="(.+?)"/', '$1="$2[__X__]"', $fieldHtml);
// Icky hax to fix certain elements with fixed ids
$fieldHtml = preg_replace('/-([a-zA-Z0-9]+?)\[__X__\]/', '[__X__]-$1', $fieldHtml);
return $fieldHtml;
}
/**
* Return the form object shown in the uploadiframe.
*/
function UploadForm() {
$form = new Form($this,'UploadForm', new FieldSet(
new HiddenField("ID", "", $this->currentPageID()),
new HiddenField("FolderID", "", $this->currentPageID()),
// needed because the button-action is triggered outside the iframe
new HiddenField("action_doUpload", "", "1"),
new FileField("Files[0]" , _t('AssetAdmin.CHOOSEFILE','Choose file: ')),
new LiteralField('UploadButton',"
<input type=\"submit\" value=\"". _t('AssetAdmin.UPLOAD', 'Upload Files Listed Below'). "\" name=\"action_upload\" id=\"Form_UploadForm_action_upload\" class=\"action\" />
"),
new LiteralField('MultifileCode',"
<p>" . _t('AssetAdmin.FILESREADY','Files ready to upload:') ."</p>
<div id=\"Form_UploadForm_FilesList\"></div>
")
), new FieldSet(
));
// Makes ajax easier
$form->disableSecurityToken();
return $form;
}
/**
* This method processes the results of the UploadForm.
* It will save the uploaded files to /assets/ and create new File objects as required.
*/
function doUpload($data, $form) {
$newFiles = array();
$fileIDs = array();
$fileNames = array();
$fileSizeWarnings = '';
$errorsArr = '';
$status = '';
$statusMessage = '';
$processedFiles = array();
foreach($data['Files'] as $param => $files) {
if(!is_array($files)) $files = array($files);
foreach($files as $key => $value) {
$processedFiles[$key][$param] = $value;
}
}
// Load POST data from arrays in to the correct dohickey.
$processedData = array();
foreach($data as $dataKey => $value) {
if ($dataKey == 'Files') continue;
if (is_array($value)) {
$i = 0;
foreach($value as $fileId => $dataValue) {
if (!isset($processedData[$i])) $processedData[$i] = array();
$processedData[$i][$dataKey] = $dataValue;
$i++;
}
}
}
$processedData = array_reverse($processedData);
if($data['FolderID'] && $data['FolderID'] != '') $folder = DataObject::get_by_id("Folder", $data['FolderID']);
else $folder = singleton('Folder');
foreach($processedFiles as $filePostId => $tmpFile) {
if($tmpFile['error'] == UPLOAD_ERR_NO_TMP_DIR) {
$errorsArr[] = _t('AssetAdmin.NOTEMP', 'There is no temporary folder for uploads. Please set upload_tmp_dir in php.ini.');
break;
}
if($tmpFile['tmp_name']) {
// Workaround open_basedir problems
if(ini_get("open_basedir")) {
$newtmp = TEMP_FOLDER . '/' . $tmpFile['name'];
move_uploaded_file($tmpFile['tmp_name'], $newtmp);
$tmpFile['tmp_name'] = $newtmp;
}
// validate files (only if not logged in as admin)
if(!File::$apply_restrictions_to_admin && Permission::check('ADMIN')) {
$valid = true;
} else {
// Set up the validator instance with rules
$validator = new Upload_Validator();
$validator->setAllowedExtensions(File::$allowed_extensions);
$validator->setAllowedMaxFileSize(self::$allowed_max_file_size);
// Do the upload validation with the rules
$upload = new Upload();
$upload->setValidator($validator);
$valid = $upload->validate($tmpFile);
if(!$valid) {
$errorsArr = $upload->getErrors();
}
}
// move file to given folder
if($valid) {
$newFile = $folder->addUploadToFolder($tmpFile);
if (isset($processedData[$filePostId])) {
$fileObject = DataObject::get_by_id('File', $newFile);
$metadataForm = new Form($this, 'MetadataForm', $fileObject->uploadMetadataFields(), new FieldSet());
$metadataForm->loadDataFrom($processedData[$filePostId]);
$metadataForm->saveInto($fileObject);
$fileObject->write();
}
$newFiles[] = $newFile;
}
}
}
if($newFiles) {
$numFiles = sizeof($newFiles);
$statusMessage = sprintf(_t('AssetAdmin.UPLOADEDX',"Uploaded %s files"),$numFiles);
$status = "good";
} else if($errorsArr) {
$statusMessage = implode('\n', $errorsArr);
$status = 'bad';
} else {
$statusMessage = _t('AssetAdmin.NOTHINGTOUPLOAD','There was nothing to upload');
$status = "";
}
$fileObj = false;
foreach($newFiles as $newFile) {
$fileIDs[] = $newFile;
$fileObj = DataObject::get_one('File', "\"File\".\"ID\"=$newFile");
// notify file object after uploading
if (method_exists($fileObj, 'onAfterUpload')) $fileObj->onAfterUpload();
$fileNames[] = $fileObj->Name;
}
// workaround for content editors image upload.Passing an extra hidden field
// in the content editors view of 'UploadMode' @see HtmlEditorField
// this will be refactored for 2.5
if(isset($data['UploadMode']) && $data['UploadMode'] == "CMSEditor" && $fileObj) {
// we can use $fileObj considering that the uploader in the cmseditor can only upload
// one file at a time. Once refactored to multiple files this is going to have to be changed
$width = (is_a($fileObj, 'Image')) ? $fileObj->getWidth() : '100';
$height = (is_a($fileObj, 'Image')) ? $fileObj->getHeight() : '100';
$values = array(
'Filename' => $fileObj->Filename,
'Width' => $width,
'Height' => $height
);
return Convert::raw2json($values);
}
$sFileIDs = implode(',', $fileIDs);
$sFileNames = implode(',', $fileNames);
echo <<<HTML
<script type="text/javascript">
var url = parent.document.getElementById('sitetree').getTreeNodeByIdx( "{$folder->ID}" ).getElementsByTagName('a')[0].href;
parent.jQuery('#Form_EditForm').entwine('ss').loadForm(url);
parent.statusMessage("{$statusMessage}","{$status}");
</script>
HTML;
}
/**
* Custom currentPage() method to handle opening the 'root' folder
*/
public function currentPage() {
$id = $this->currentPageID();
if($id && is_numeric($id)) {
return DataObject::get_by_id('File', $id);
} else if($id == 'root') {
return singleton('File');
}
}
public function SiteTreeAsUL() {
return $this->getSiteTreeFor($this->stat('tree_class'), null, 'ChildFolders');
}
function getSiteTreeFor($className, $rootID = null, $childrenMethod = null, $numChildrenMethod = null, $filterFunction = null, $minNodeCount = 30) {
if (!$childrenMethod) $childrenMethod = 'ChildFolders';
return parent::getSiteTreeFor($className, $rootID, $childrenMethod, $numChildrenMethod, $filterFunction, $minNodeCount);
}
public function getCMSTreeTitle() {
return Director::absoluteBaseURL() . "assets";
}
//------------------------------------------------------------------------------------------//
// Data saving handlers
/**
* @return Form
*/
function SyncForm() {
$form = new Form(
$this,
'SyncForm',
new FieldSet(
),
new FieldSet(
$btn = new FormAction('doSync', _t('FILESYSTEMSYNC','Look for new files'))
)
);
$form->addExtraClass('actionparams');
$form->setFormMethod('GET');
$form->setFormAction('dev/tasks/FilesystemSyncTask');
$btn->describe(_t('AssetAdmin_left.ss.FILESYSTEMSYNC_DESC', 'SilverStripe maintains its own database of the files &amp; images stored in your assets/ folder. Click this button to update that database, if files are added to the assets/ folder from outside SilverStripe, for example, if you have uploaded files via FTP.'));
return $form;
}
/**
* #################################
* Garbage collection.
* #################################
*/
/**
* Removes all unused thumbnails from the file store
* and returns the status of the process to the user.
*/
public function deleteunusedthumbnails() {
$count = 0;
$thumbnails = $this->getUnusedThumbnails();
if($thumbnails) {
foreach($thumbnails as $thumbnail) {
unlink(ASSETS_PATH . "/" . $thumbnail);
$count++;
}
}
$message = sprintf(_t('AssetAdmin.THUMBSDELETED', '%s unused thumbnails have been deleted'), $count);
FormResponse::status_message($message, 'good');
echo FormResponse::respond();
}
/**
* Creates array containg all unused thumbnails.
*
* Array is created in three steps:
* 1. Scan assets folder and retrieve all thumbnails
* 2. Scan all HTMLField in system and retrieve thumbnails from them.
* 3. Count difference between two sets (array_diff)
*
* @return array
*/
private function getUnusedThumbnails() {
$allThumbnails = array();
$usedThumbnails = array();
$dirIterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(ASSETS_PATH));
$classes = ClassInfo::subclassesFor('SiteTree');
if($dirIterator) {
foreach($dirIterator as $file) {
if($file->isFile()) {
if(strpos($file->getPathname(), '_resampled') !== false) {
$pathInfo = pathinfo($file->getPathname());
if(in_array(strtolower($pathInfo['extension']), array('jpeg', 'jpg', 'jpe', 'png', 'gif'))) {
$path = str_replace('\\','/', $file->getPathname());
$allThumbnails[] = substr($path, strpos($path, '/assets/') + 8);
}
}
}
}
}
if($classes) {
foreach($classes as $className) {
$SNG_class = singleton($className);
$objects = DataObject::get($className);
if($objects !== NULL) {
foreach($objects as $object) {
foreach($SNG_class->db() as $fieldName => $fieldType) {
if($fieldType == 'HTMLText') {
$url1 = HTTP::findByTagAndAttribute($object->$fieldName,array('img' => 'src'));
if($url1 != NULL) {
$usedThumbnails[] = substr($url1[0], strpos($url1[0], '/assets/') + 8);
}
if($object->latestPublished > 0) {
$object = Versioned::get_latest_version($className, $object->ID);
$url2 = HTTP::findByTagAndAttribute($object->$fieldName, array('img' => 'src'));
if($url2 != NULL) {
$usedThumbnails[] = substr($url2[0], strpos($url2[0], '/assets/') + 8);
}
}
}
}
}
}
}
}
return array_diff($allThumbnails, $usedThumbnails);
}
}
/**
* Delete multiple {@link Folder} records (and the associated filesystem nodes).
* Usually used through the {@link AssetAdmin} interface.
*
* @package cms
* @subpackage batchactions
*/
class AssetAdmin_DeleteBatchAction extends CMSBatchAction {
function getActionTitle() {
// _t('AssetAdmin_left.ss.SELECTTODEL','Select the folders that you want to delete and then click the button below')
return _t('AssetAdmin_DeleteBatchAction.TITLE', 'Delete folders');
}
function run(DataObjectSet $records) {
$status = array(
'modified'=>array(),
'deleted'=>array()
);
foreach($records as $record) {
$id = $record->ID;
// Perform the action
if($record->canDelete()) $record->delete();
$status['deleted'][$id] = array();
$record->destroy();
unset($record);
}
return Convert::raw2json($status);
}
}
?>