diff --git a/core/model/Image.php b/core/model/Image.php index d30f221b6..a86eec5b2 100755 --- a/core/model/Image.php +++ b/core/model/Image.php @@ -466,336 +466,3 @@ class Image_Cached extends Image { } } -/** - * Uploader support for the uploading anything which is a File or subclass of File, eg Image. - * Is connected to the URL routing "/image" through sapphire/_config.php, - * and used by all iframe-based upload-fields in the CMS. - * - * Used by {@link FileIFrameField}, {@link ImageField}. - * - * @todo Refactor to using FileIFrameField and ImageField as a controller for the upload, - * rather than something totally disconnected from the original Form and FormField - * context. Without the original context its impossible to control permissions etc. - * - * @package sapphire - * @subpackage filesystem - */ -class Image_Uploader extends Controller { - static $url_handlers = array( - '$Action!/$Class!/$ID!/$Field!/$FormName!' => '$FormName', - '$Action/$Class/$ID/$Field' => 'handleAction', - ); - - static $allowed_actions = array( - 'iframe' => 'CMS_ACCESS_CMSMain', - 'flush' => 'CMS_ACCESS_CMSMain', - 'save' => 'CMS_ACCESS_CMSMain', - 'delete' => 'CMS_ACCESS_CMSMain', - 'EditImageForm' => 'CMS_ACCESS_CMSMain', - 'DeleteImageForm' => 'CMS_ACCESS_CMSMain' - ); - - function init() { - // set language - $member = Member::currentUser(); - if(!empty($member->Locale)) { - i18n::set_locale($member->Locale); - } - - // set reading lang - if(singleton('SiteTree')->hasExtension('Translatable') && !Director::is_ajax()) { - Translatable::choose_site_locale(array_keys(Translatable::get_existing_content_languages('SiteTree'))); - } - - parent::init(); - } - - /** - * Ensures the css is loaded for the iframe. - */ - function iframe() { - if(!Permission::check('CMS_ACCESS_CMSMain')) Security::permissionFailure($this); - - Requirements::css(CMS_DIR . "/css/Image_iframe.css"); - return array(); - } - - /** - * Image object attached to this class. - * @var Image - */ - protected $imageObj; - - /** - * Associated parent object. - * @var DataObject - */ - protected $linkedObj; - - /** - * Finds the associated parent object from the urlParams. - * @return DataObject - */ - function linkedObj() { - if(!$this->linkedObj) { - $this->linkedObj = DataObject::get_by_id($this->urlParams['Class'], $this->urlParams['ID']); - if(!$this->linkedObj) { - user_error("Data object '{$this->urlParams['Class']}.{$this->urlParams['ID']}' couldn't be found", E_USER_ERROR); - } - } - return $this->linkedObj; - } - - /** - * Returns the Image object attached to this class. - * @return Image - */ - function Image() { - if(!$this->imageObj) { - $funcName = $this->urlParams['Field']; - $linked = $this->linkedObj(); - $this->imageObj = $linked->obj($funcName); - if(!$this->imageObj) {$this->imageObj = new Image(null);} - } - - return $this->imageObj; - } - - /** - * Returns true if the file attachment is an image. - * Otherwise, it's a file. - * @return boolean - */ - function IsImage() { - $className = $this->Image()->class; - return $className == "Image" || is_subclass_of($className, "Image"); - } - - function UseSimpleForm() { - if(!$this->useSimpleForm) { - $this->useSimpleForm = false; - } - return $this->useSimpleForm; - } - - /** - * Return a link to this uploader. - * @return string - */ - function Link($action = null) { - return $this->RelativeLink($action); - } - - /** - * Return the relative link to this uploader. - * @return string - */ - function RelativeLink($action = null) { - if(!$action) { - $action = "index"; - } - return "images/$action/{$this->urlParams['Class']}/{$this->urlParams['ID']}/{$this->urlParams['Field']}"; - } - - /** - * Form to show the current image and allow you to upload another one. - * @return Form - */ - function EditImageForm() { - $isImage = $this->IsImage(); - $type = $isImage ? _t('Controller.IMAGE', "Image") : _t('Controller.FILE', "File"); - if($this->Image()->ID) { - $title = sprintf( - _t('ImageUploader.REPLACE', "Replace %s", PR_MEDIUM, 'Replace file/image'), - $type - ); - $fromYourPC = _t('ImageUploader.ONEFROMCOMPUTER', "With one from your computer"); - $fromTheDB = _t('ImageUplaoder.ONEFROMFILESTORE', "With one from the file store"); - } else { - $title = sprintf( - _t('ImageUploader.ATTACH', "Attach %s", PR_MEDIUM, 'Attach image/file'), - $type - ); - $fromYourPC = _t('ImageUploader.FROMCOMPUTER', "From your computer"); - $fromTheDB = _t('ImageUploader.FROMFILESTORE', "From the file store"); - } - return new Form( - $this, - 'EditImageForm', - new FieldSet( - new HiddenField("Class", null, $this->urlParams['Class']), - new HiddenField("ID", null, $this->urlParams['ID']), - new HiddenField("Field", null, $this->urlParams['Field']), - new HeaderField('EditImageHeader',$title), - new SelectionGroup("ImageSource", array( - "new//$fromYourPC" => new FieldGroup("", - new FileField("Upload","") - ), - "existing//$fromTheDB" => new FieldGroup("", - new TreeDropdownField("ExistingFile", "","File") - ) - )) - ), - new FieldSet( - new FormAction("save",$title) - ) - ); - } - - /** - * A simple version of the upload form. - * @returns string - */ - function EditImageSimpleForm() { - $isImage = $this->IsImage(); - $type = $isImage ? _t('Controller.IMAGE') : _t('Controller.FILE'); - if($this->Image()->ID) { - $title = sprintf( - _t('ImageUploader.REPLACE'), - $type - ); - $fromYourPC = _t('ImageUploader.ONEFROMCOMPUTER'); - } else { - $title = sprintf( - _t('ImageUploader.ATTACH'), - $type - ); - $fromTheDB = _t('ImageUploader.ONEFROMFILESTORE'); - } - - return new Form($this, 'EditImageSimpleForm', new FieldSet( - new HiddenField("Class", null, $this->urlParams['Class']), - new HiddenField("ID", null, $this->urlParams['ID']), - new HiddenField("Field", null, $this->urlParams['Field']), - new FileField("Upload","") - ), - new FieldSet( - new FormAction("save",$title) - )); - } - - /** - * A form to delete this image. - * @return string - */ - function DeleteImageForm() { - if($this->Image()->ID) { - $isImage = $this->IsImage(); - $type = $isImage ? _t('Controller.IMAGE') : _t('Controller.FILE'); - $title = sprintf( - _t('ImageUploader.DELETE', 'Delete %s', PR_MEDIUM, 'Delete file/image'), - $type - ); - $form = new Form( - $this, - 'DeleteImageForm', - new FieldSet( - new HiddenField("Class", null, $this->urlParams['Class']), - new HiddenField("ID", null, $this->urlParams['ID']), - new HiddenField("Field", null, $this->urlParams['Field']) - ), - new FieldSet( - $deleteAction = new ConfirmedFormAction( - "delete", - $title, - sprintf(_t('ImageUploader.REALLYDELETE', "Do you really want to remove this %s?"), $type) - ) - ) - ); - $deleteAction->addExtraClass('delete'); - - return $form; - } - } - - /** - * Save the data in this form. - */ - function save($data, $form) { - if($data['ImageSource'] != 'existing' && $data['Upload']['size'] == 0) { - // No image has been uploaded - Director::redirectBack(); - return; - } - $owner = DataObject::get_by_id($data['Class'], $data['ID']); - $fieldName = $data['Field'] . 'ID'; - - if($data['ImageSource'] == 'existing') { - if(!$data['ExistingFile']) { - // No image has been selected - Director::redirectBack(); - return; - } - - $owner->$fieldName = $data['ExistingFile']; - - // Edit the class name, if applicable - $existingFile = DataObject::get_by_id("File", $data['ExistingFile']); - $desiredClass = $owner->has_one($data['Field']); - - // Unless specifically asked, we don't want the user to be able - // to select a folder - if(is_a($existingFile, 'Folder') && $desiredClass != 'Folder') { - Director::redirectBack(); - return; - } - - if(!is_a($existingFile, $desiredClass)) { - $existingFile->ClassName = $desiredClass; - $existingFile->write(); - } - } else { - // TODO We need to replace this with a way to get the type of a field - $imageClass = $owner->has_one($data['Field']); - - // If we can't find the relationship, assume its an Image. - if( !$imageClass) $imageClass = 'Image'; - - // Assuming its a decendant of File - $image = new $imageClass(); - $image->loadUploaded($data['Upload']); - $owner->$fieldName = $image->ID; - - // store the owner id with the uploaded image - $member = Member::currentUser(); - $image->OwnerID = $member->ID; - $image->write(); - } - - $owner->write(); - Director::redirectBack(); - } - - /** - * Delete the image referenced by this form. - */ - function delete($data, $form) { - $owner = DataObject::get_by_id( $data[ 'Class' ], $data[ 'ID' ] ); - $fieldName = $data[ 'Field' ] . 'ID'; - $owner->$fieldName = 0; - $owner->write(); - Director::redirect($this->Link('iframe')); - } - - /** - * Flush all of the generated images. - */ - function flush() { - if(!Permission::check('ADMIN')) Security::permissionFailure($this); - - $images = DataObject::get("Image",""); - $numItems = 0; - $num = 0; - - foreach($images as $image) { - $numDeleted = $image->deleteFormattedImages(); - if($numDeleted) { - $numItems++; - } - $num += $numDeleted; - } - echo $num . ' formatted images from ' . $numItems . ' items flushed'; - } -} - -?> diff --git a/css/FileIFrameField.css b/css/FileIFrameField.css new file mode 100644 index 000000000..0f1868608 --- /dev/null +++ b/css/FileIFrameField.css @@ -0,0 +1,167 @@ +@import url("typography.css"); + +html,body { + padding: 0; + margin: 0; + border-style: none; + height: 100%; + overflow: hidden; +} + +form { + margin: 0; padding: 0; +} + +h2 { + margin: 0; + font-size: 1.4em; +} + +/** + * Selection Groups + */ +.SelectionGroup { + padding: 0; + margin: 10px 0 0 0; +} +.SelectionGroup li { + list-style-type: none; + margin: 0 0 4px; +} +.SelectionGroup li label { + font-size: 11px; +} +.SelectionGroup li input.selector { + width: 20px; + margin-top: 0; +} + + +.SelectionGroup li div.field { + display: none; +} +.SelectionGroup li.selected div.field { + display: block; + margin-left: 30px; + margin-bottom: 1em; + margin-top: 4px; +} +.mainblock .SelectionGroup li.selected div.field { + margin-left: 27px; + margin-bottom: 4px; +} + +.SelectionGroup li.selected label.selector { + font-weight: bold; +} + + + +/** + * TreeDropdownField stying + */ +.SelectionGroup div.TreeDropdownField { + width: 241px; + padding: 0; +} +html>body div.TreeDropdownField { + position:relative; +} + +.SelectionGroup div.TreeDropdownField span.items { + display: block; + height: 100%; + border: 1px #7f9db9 solid; + cursor: pointer; + width: 214px; + float: left; + padding-top: 2px; + padding-bottom: 2px; + background-color: white; +} + +.SelectionGroup div.TreeDropdownField div.tree_holder { + clear: left; + cursor: default; + border: 1px black solid; + margin: 0; + height: 180px; + overflow: auto; + background-color: white; + /** + * HACK IE6, see http://www.hedgerwow.com/360/bugs/css-select-free.html + */ + position:absolute; + z-index:10; + width:238px;/*must have for any value*/ +} + +html>body div.TreeDropdownField div.tree_holder { + top: 20px; + left: 0px; + z-index: 1000; +} + +/** + * HACK IE6, see http://www.hedgerwow.com/360/bugs/css-select-free.html + */ +.SelectionGroup div.TreeDropdownField div.tree_holder iframe { + display:none;/* IE5*/ + display/**/:block;/* IE5*/ + position:absolute; + top:0; + left:0; + z-index:-1; + filter:mask(); + width:180px; /*must have for any big value*/ + height:200px/*must have for any big value*/; + border: 0; +} + +div.TreeDropdownField a.editLink { + border-width: 1px 1px 1px 0; + background: url(../../sapphire/images/TreeDropdownField_button.gif) left top no-repeat; + width: 19px; + height: 21px; + margin: 0; + padding: 0; + float: left; + clear: right; + z-index: 0; + overflow: hidden; +} + +/* added block/width so tree values don't disappear in ie7 */ +.SelectionGroup div.TreeDropdownField ul.tree li { + display: block; + width: 100%; +} + +.Actions { + text-align: right; + margin: 0; + position: absolute; + right: 5px; + bottom: 5px; +} + +.mainblock { + float: left; + border: 1px #CCC solid; + padding: 5px; + margin-right: 5px; + height: 140px; + position: relative; +} + + .mainblock.editform { + width: 290px; + } + +.mainblock form fieldset { + border: none; +} + +.mainblock form div.Actions input { + font-size: 11px; +} \ No newline at end of file diff --git a/filesystem/FlushGeneratedImagesTask.php b/filesystem/FlushGeneratedImagesTask.php new file mode 100644 index 000000000..513575b91 --- /dev/null +++ b/filesystem/FlushGeneratedImagesTask.php @@ -0,0 +1,44 @@ +deleteFormattedImages()) { + $removedItems += $deleted; + } + + $processedImages++; + } + + echo "Removed $removedItems generated images from $processedImages Image objects stored in the Database."; + } + +} \ No newline at end of file diff --git a/forms/FileIFrameField.php b/forms/FileIFrameField.php index 606b64948..7be92babb 100755 --- a/forms/FileIFrameField.php +++ b/forms/FileIFrameField.php @@ -1,35 +1,228 @@ form->getRecord(); - - if($data && $data->ID && is_numeric($data->ID)) { - $idxField = $this->name . 'ID'; - $hiddenField = "id() . "\" name=\"$idxField\" value=\"" . $this->attrValue() . "\" />"; - - $parentClass = $data->class; - - $parentID = $data->ID; - $parentField = $this->name; - $iframe = ""; + public static $allowed_actions = array ( + 'iframe', + 'EditFileForm', + 'DeleteFileForm' + ); - return $iframe . $hiddenField; - - } else { - $this->value = _t('FileIFrameField.NOTEADDFILES', 'You can add files once you have saved for the first time.'); - return FormField::Field(); - } + /** + * @see FileField::__construct() + */ + public function __construct($name, $title = null, $value = null, $form = null, $rightTitle = null, $folderName = null) { + Requirements::css(THIRDPARTY_DIR . '/jquery/themes/default/ui.all.css'); + + Requirements::add_i18n_javascript(SAPPHIRE_DIR . '/javascript/lang'); + Requirements::javascript(THIRDPARTY_DIR . '/jquery/jquery.js'); + Requirements::javascript(THIRDPARTY_DIR . '/jquery/ui/ui.core.js'); + Requirements::javascript(THIRDPARTY_DIR . '/jquery/ui/ui.dialog.js'); + + parent::__construct($name, $title, $value, $form, $rightTitle, $folderName); } - -} -?> \ No newline at end of file + + /** + * @return string + */ + public function Field() { + if($this->form->getRecord() && $this->form->getRecord()->exists()) { + return $this->createTag ( + 'iframe', + array ( + 'name' => $this->Name() . '_iframe', + 'src' => Controller::join_links($this->Link(), 'iframe'), + 'style' => 'height: 152px; width: 100%; border: none;' + ) + ) . $this->createTag ( + 'input', + array ( + 'type' => 'hidden', + 'id' => $this->ID(), + 'name' => $this->Name() . 'ID', + 'value' => $this->attrValue() + ) + ); + } + + $this->setValue(sprintf(_t ( + 'FileIFrameField.ATTACHONCESAVED', '%ss can be attached once you have saved the record for the first time.' + ), $this->FileTypeName())); + + return FormField::field(); + } + + /** + * Attempt to retreive a File object that has already been attached to this forms data record + * + * @return File|null + */ + public function AttachedFile() { + return $this->form->getRecord()->has_one($this->Name()) ? $this->form->getRecord()->{$this->Name()}() : null; + } + + /** + * @return string + */ + public function iframe() { + // clear the requirements added by any parent controllers + Requirements::clear(); + Requirements::add_i18n_javascript('sapphire/javascript/lang'); + Requirements::javascript('jsparty/jquery/jquery.js'); + Requirements::javascript('sapphire/javascript/FileIFrameField.js'); + + Requirements::css('cms/css/typography.css'); + Requirements::css('sapphire/css/FileIFrameField.css'); + + return $this->renderWith('FileIFrameField'); + } + + /** + * @return Form + */ + public function EditFileForm() { + $uploadFile = _t('FileIFrameField.FROMCOMPUTER', 'From your Computer'); + $selectFile = _t('FileIFrameField.FROMFILESTORE', 'From the File Store'); + + if($this->AttachedFile() && $this->AttachedFile()->ID) { + $title = sprintf(_t('FileIFrameField.REPLACE', 'Replace %s'), $this->FileTypeName()); + } else { + $title = sprintf(_t('FileIFrameField.ATTACH', 'Attach %s'), $this->FileTypeName()); + } + + $fileSources = array(); + + if(singleton('File')->canCreate()) { + $fileSources["new//$uploadFile"] = new FileField('Upload', ''); + } + + $fileSources["existing//$selectFile"] = new TreeDropdownField('ExistingFile', '', 'File'); + + return new Form ( + $this, + 'EditFileForm', + new FieldSet ( + new HeaderField('EditFileHeader', $title), + new SelectionGroup('FileSource', $fileSources) + ), + new FieldSet ( + new FormAction('save', $title) + ) + ); + } + + public function save($data, $form) { + // check the user has entered all the required information + if ( + !isset($data['FileSource']) + || ($data['FileSource'] == 'new' && (!isset($_FILES['Upload']) || !$_FILES['Upload'])) + || ($data['FileSource'] == 'existing' && (!isset($data['ExistingFile']) || !$data['ExistingFile'])) + ) { + Director::redirectBack(); + return; + } + + if($this->form->getRecord()->has_one($this->Name())) { + $desiredClass = $this->form->getRecord()->has_one($this->Name()); + } else { + $desiredClass = 'File'; + } + + // upload a new file + if($data['FileSource'] == 'new') { + $fileObject = Object::create($desiredClass); + + $this->upload->setAllowedExtensions($this->allowedExtensions); + $this->upload->setAllowedMaxFileSize($this->allowedMaxFileSize); + + $this->upload->loadIntoFile($_FILES['Upload'], $fileObject, $this->folderName); + + if($this->upload->isError()) { + Director::redirectBack(); + return; + } + + $this->form->getRecord()->{$this->Name() . 'ID'} = $fileObject->ID; + + $fileObject->OwnerID = (Member::currentUser() ? Member::currentUser()->ID : 0); + $fileObject->write(); + } + + // attach an existing image from the assets store + if($data['FileSource'] == 'existing') { + $fileObject = DataObject::get_by_id('File', $data['ExistingFile']); + + // dont allow the user to attach a folder by default + if(!$fileObject || ($fileObject instanceof Folder && $desiredClass != 'Folder')) { + Director::redirectBack(); + return; + } + + $this->form->getRecord()->{$this->Name() . 'ID'} = $fileObject->ID; + + if(!$fileObject instanceof $desiredClass) { + $fileObject->ClassName = $desiredClass; + $fileObject->write(); + } + } + + $this->form->getRecord()->write(); + Director::redirectBack(); + } + + /** + * @return Form + */ + public function DeleteFileForm() { + $form = new Form ( + $this, + 'DeleteFileForm', + new FieldSet ( + new HiddenField('DeleteFile', null, false) + ), + new FieldSet ( + $deleteButton = new FormAction ( + 'delete', sprintf(_t('FileIFrameField.DELETE', 'Delete %s'), $this->FileTypeName()) + ) + ) + ); + + $deleteButton->addExtraClass('delete'); + return $form; + } + + public function delete($data, $form) { + // delete the actual file, or just un-attach it? + if(isset($data['DeleteFile']) && $data['DeleteFile']) { + $file = DataObject::get_by_id('File', $this->form->getRecord()->{$this->Name() . 'ID'}); + + if($file) { + $file->delete(); + } + } + + // then un-attach file from this record + $this->form->getRecord()->{$this->Name() . 'ID'} = 0; + $this->form->getRecord()->write(); + + Director::redirectBack(); + } + + /** + * Get the type of file this field is used to attach (e.g. File, Image) + * + * @return string + */ + public function FileTypeName() { + return _t('FileIFrameField.FILE', 'File'); + } + +} \ No newline at end of file diff --git a/forms/ImageField.php b/forms/ImageField.php index 5c2c9e1ec..c08b84bfc 100755 --- a/forms/ImageField.php +++ b/forms/ImageField.php @@ -1,62 +1,25 @@ form->getRecord(); - - if($id && is_numeric($id)) { - $parentID = $id; - } elseif($data) { - $parentID = $data->ID; - } else { - $parentID = null; - } - - if($data && $parentID && is_numeric($parentID)) { - $idxField = $this->name . 'ID'; - $hiddenField = "id() . "\" name=\"$idxField\" value=\"" . $this->attrValue() . "\" />"; - - $parentClass = $data->class; - $parentField = $this->name; - - $iframe = ""; - - return $iframe . $hiddenField; - } else { - $this->value = _t('ImageField.NOTEADDIMAGES', 'You can add images once you have saved for the first time.'); - return FormField::Field(); - } - } - - - public function saveInto($record) { - $data = $this->form->getRecord(); - // if the record was written for the first time (has an arbitrary "new"-ID), - // update the imagefield to enable uploading - if($record->ID && $data && substr($data->ID, 0, 3) == 'new') { - FormResponse::update_dom_id($this->id(), $this->Field($record->ID)); - } - } - - +class ImageField extends FileIFrameField { + /** - * Returns a readonly version of this field + * @return SimpleImageField_Disabled */ - function performReadonlyTransformation() { - $field = new SimpleImageField_Disabled($this->name, $this->title, $this->value); - $field->setForm($this->form); - return $field; + public function performReadonlyTransformation() { + return new SimpleImageField_Disabled($this->name, $this->title, $this->value, $this->form); } -} - -?> \ No newline at end of file + + /** + * @return string + */ + public function FileTypeName() { + return _t('ImageField.IMAGE', 'Image'); + } + +} \ No newline at end of file diff --git a/javascript/FileIFrameField.js b/javascript/FileIFrameField.js new file mode 100644 index 000000000..8982c7b75 --- /dev/null +++ b/javascript/FileIFrameField.js @@ -0,0 +1,47 @@ +(function($) { + + $('#Form_DeleteFileForm_action_delete').click(function(e) { + var deleteMessage = ss.i18n._t('FILEIFRAMEFIELD.CONFIRMDELETE', 'Are you sure you want to delete this file?'); + + if(typeof(parent.jQuery.fn.dialog) != 'undefined') { + var buttons = {}; + var $dialog = undefined; + var $deleteForm = $('#Form_DeleteFileForm'); + var $deleteFile = $('#Form_DeleteFileForm_DeleteFile'); + + buttons[ss.i18n._t('FILEIFRAMEFIELD.DELETEFILE', 'Delete File')] = function() { + $deleteFile.attr('value', 'true'); + $deleteForm.submit(); + + $dialog.dialog('close'); + }; + + buttons[ss.i18n._t('FILEIFRAMEFIELD.UNATTACHFILE', 'Un-Attach File')] = function() { + $deleteForm.submit(); + $dialog.dialog('close'); + }; + + buttons[ss.i18n._t('CANCEL', 'Cancel')] = function() { + $dialog.dialog('close'); + }; + + $dialog = parent.jQuery('

' + deleteMessage + '

').dialog({ + bgiframe: true, + resizable: false, + modal: true, + height: 140, + overlay: { + backgroundColor: '#000', + opacity: 0.5 + }, + title: ss.i18n._t('FILEIFRAMEFIELD.DELETEIMAGE', 'Delete Image'), + buttons: buttons + }); + + e.preventDefault(); + } else if(!confirm(deleteMessage)) { + e.preventDefault(); + } + }); + +})(jQuery); \ No newline at end of file diff --git a/templates/FileIFrameField.ss b/templates/FileIFrameField.ss new file mode 100644 index 000000000..0a3de4911 --- /dev/null +++ b/templates/FileIFrameField.ss @@ -0,0 +1,25 @@ + + + + <% base_tag %> + + <% _t('TITLE', 'Image Uploading Iframe') %> + + + +
+ $EditFileForm +
+ + <% if AttachedFile.ID %> +
+ $AttachedFile.CMSThumbnail + + <% if DeleteFileForm %> + $DeleteFileForm + <% end_if %> +
+ <% end_if %> + + + \ No newline at end of file