4 * AssetAdmin is the 'file store' section of the CMS.
5 * It provides an interface for maniupating the File and Folder objects in the system.
7 class AssetAdmin
extends LeftAndMain
{
8 static $tree_class = "File";
10 public function Link($action=null) {
11 if(!$action) $action = "index";
12 return "admin/assets/$action/" . $this->currentPageID();
16 * Return fake-ID "root" if no ID is found (needed to upload files into the root-folder)
18 public function currentPageID() {
19 if(isset($_REQUEST['ID']) && is_numeric($_REQUEST['ID'])) {
20 return $_REQUEST['ID'];
21 } elseif (is_numeric($this->urlParams
['ID'])) {
22 return $this->urlParams
['ID'];
23 } elseif(is_numeric(Session
::get("{$this->class}.currentPage"))) {
24 return Session
::get("{$this->class}.currentPage");
31 * Set up the controller, in particular, re-sync the File database with the assets folder./
36 // needed for MemberTableField (Requirements not determined before Ajax-Call)
37 Requirements
::javascript("sapphire/javascript/ComplexTableField.js");
38 Requirements
::css("jsparty/greybox/greybox.css");
39 Requirements
::css("sapphire/css/ComplexTableField.css");
41 Requirements
::javascript("cms/javascript/AssetAdmin.js");
42 Requirements
::javascript("cms/javascript/AssetAdmin_left.js");
43 Requirements
::javascript("cms/javascript/AssetAdmin_right.js");
45 Requirements
::javascript("cms/javascript/Upload.js");
46 Requirements
::javascript("sapphire/javascript/Security_login.js");
47 Requirements
::javascript("jsparty/SWFUpload/SWFUpload.js");
49 // Requirements::javascript('sapphire/javascript/TableListField.js');
51 // Include the right JS]
52 // Hayden: This didn't appear to be used at all
53 /*$fileList = new FileList("Form_EditForm_Files", null);
54 $fileList->setClick_AjaxLoad('admin/assets/getfile/', 'Form_SubForm');
55 $fileList->FieldHolder();*/
57 Requirements
::javascript("jsparty/greybox/AmiJS.js");
58 Requirements
::javascript("jsparty/greybox/greybox.js");
59 Requirements
::css("jsparty/greybox/greybox.css");
63 * Display the upload form. Returns an iframe tag that will show admin/assets/uploadiframe.
65 function getUploadIframe() {
67 <iframe name="AssetAdmin_upload" src="admin/assets/uploadiframe/{$this->urlParams['ID']}" id="AssetAdmin_upload" border="0" style="border-style: none; width: 100%; height: 200px">
78 * Show the content of the upload iframe. The form is specified by a template.
80 function uploadiframe() {
81 Requirements
::clear();
83 Requirements
::javascript("jsparty/prototype.js");
84 Requirements
::javascript("jsparty/loader.js");
85 Requirements
::javascript("jsparty/behaviour.js");
86 Requirements
::javascript("jsparty/prototype_improvements.js");
87 Requirements
::javascript("jsparty/layout_helpers.js");
88 Requirements
::javascript("cms/javascript/LeftAndMain.js");
89 Requirements
::javascript("jsparty/multifile/multifile.js");
90 Requirements
::css("jsparty/multifile/multifile.css");
91 Requirements
::css("cms/css/typography.css");
92 Requirements
::css("cms/css/layout.css");
93 Requirements
::css("cms/css/cms_left.css");
94 Requirements
::css("cms/css/cms_right.css");
96 if(isset($data['ID']) && $data['ID'] != 'root') $folder = DataObject
::get_by_id("Folder", $data['ID']);
97 else $folder = singleton('Folder');
99 $canUpload = $folder->userCanEdit();
101 return array( 'CanUpload' => $canUpload );
105 * Return the form object shown in the uploadiframe.
107 function UploadForm() {
109 return new Form($this,'UploadForm', new FieldSet(
110 new HiddenField("ID", "", $this->currentPageID()),
111 // needed because the button-action is triggered outside the iframe
112 new HiddenField("action_doUpload", "", "1"),
113 new FileField("Files[0]" , "Choose file "),
114 new LiteralField('UploadButton',"
115 <input type='submit' value='Upload Files Listed Below' name='action_upload' id='Form_UploadForm_action_upload' class='action' />
117 new LiteralField('MultifileCode',"
118 <p>Files ready to upload:</p>
119 <div id='Form_UploadForm_FilesList'></div>
121 var multi_selector = new MultiSelector($('Form_UploadForm_FilesList'), null, $('Form_UploadForm_action_upload'));
122 multi_selector.addElement($('Form_UploadForm_Files-0'));
123 new window.top.document.Upload.initialize();
132 * This method processes the results of the UploadForm.
133 * It will save the uploaded files to /assets/ and create new File objects as required.
135 function doUpload($data, $form) {
136 foreach($data['Files'] as $param => $files) {
137 if(!is_array($files)) $files = array($files);
138 foreach($files as $key => $value) {
139 $processedFiles[$key][$param] = $value;
143 if($data['ID'] && $data['ID'] != 'root') $folder = DataObject
::get_by_id("Folder", $data['ID']);
144 else $folder = singleton('Folder');
146 $warnFiles = array();
148 foreach($processedFiles as $file) {
149 if($file['tmp_name']) {
150 // check that the file can be uploaded and isn't too large
152 $extensionIndex = strripos( $file['name'], '.' );
153 $extension = strtolower( substr( $file['name'], $extensionIndex +
1 ) );
155 if( $extensionIndex !== FALSE )
156 list( $maxSize, $warnSize ) = File
::getMaxFileSize( $extension );
158 list( $maxSize, $warnSize ) = File
::getMaxFileSize();
160 // check that the file is not too large or that the current user is an administrator
161 if( $this->can('AdminCMS') ||
( File
::allowedFileType( $extension ) && (!isset($maxsize) ||
$file['size'] < $maxSize)))
162 $newFiles[] = $folder->addUploadToFolder($file);
163 elseif( !File
::allowedFileType( $extension ) ) {
164 $fileSizeWarnings .= "alert( 'Only administrators can upload $extension files.' );";
166 if( $file['size'] > 1048576 )
167 $fileSize = "" . ceil( $file['size'] / 1048576 ) . "MB";
168 elseif( $file['size'] > 1024 )
169 $fileSize = "" . ceil( $file['size'] / 1024 ) . "KB";
171 $fileSize = "" . ceil( $file['size'] ) . "B";
174 $fileSizeWarnings .= "alert( '\\'" . $file['name'] . "\\' is too large ($fileSize). Files of this type cannot be larger than $warnSize ' );";
180 $numFiles = sizeof($newFiles);
181 $statusMessage = "Uploaded $numFiles files";
184 $statusMessage = "There was nothing to upload";
188 <script type="text/javascript">
189 var form = parent.document.getElementById('Form_EditForm');
190 form.getPageFromServer(form.elements.ID.value);
191 parent.statusMessage("{$statusMessage}","{$status}");
193 parent.document.getElementById('sitetree').getTreeNodeByIdx( "{$folder->ID}" ).getElementsByTagName('a')[0].className += ' contents';
199 * Needs to be overridden to make sure an ID with value "0" is still valid (rootfolder)
204 * Return the form that displays the details of a folder, including a file list and fields for editing the folder name.
206 function getEditForm($id) {
207 if($id && $id != "root") {
208 $record = DataObject
::get_by_id("File", $id);
210 $record = singleton("Folder");
213 $fileList = new AssetTableField(
217 array("Title" => "Title", "LinkedURL" => "Filename"),
220 $fileList->setFolder($record);
221 $fileList->setPopupCaption("View/Edit Asset");
224 $nameField = ($id != "root") ?
new TextField("Name", "Folder Name") : new HiddenField("Name");
225 if( $record->userCanEdit() ) {
226 $deleteButton = new InlineFormAction('deletemarked',"Delete selected files", 'delete');
227 $deleteButton->includeDefaultJS(false);
229 $deleteButton = new HiddenField('deletemarked');
232 $fields = new FieldSet(
233 new HiddenField("Title"),
239 new HiddenField("FileIDs"),
240 new HiddenField("DestFolderID")
243 new ReadonlyField("URL"),
244 new ReadonlyField("ClassName", "Type"),
245 new ReadonlyField("Created", "First Uploaded"),
246 new ReadonlyField("LastEdited", "Last Updated")
249 new LiteralField("UploadIframe",
250 $this->getUploadIframe()
254 new HiddenField("ID")
257 $actions = new FieldSet();
259 // Only show save button if not 'assets' folder
260 if( $record->userCanEdit() && $id != "root") {
261 $actions = new FieldSet(
262 new FormAction('save',"Save folder name")
266 $form = new Form($this, "EditForm", $fields, $actions);
268 $form->loadDataFrom($record);
270 $form->loadDataFrom(array(
272 "URL" => Director
::absoluteBaseURL() . 'assets/',
276 // @todo: These workflow features aren't really appropriate for all projects
277 if( Member
::currentUser()->_isAdmin() && project() == 'mot' ) {
278 $fields->addFieldsToTab( 'Root.Workflow', new DropdownField("Owner", "Owner", Member
::map() ) );
279 $fields->addFieldsToTab( 'Root.Workflow', new TreeMultiselectField("CanUse", "Content usable by") );
280 $fields->addFieldsToTab( 'Root.Workflow', new TreeMultiselectField("CanEdit", "Content modifiable by") );
283 if( !$record->userCanEdit() )
284 $form->makeReadonly();
292 * Perform the "move marked" action.
293 * Called and returns in same way as 'save' function
295 public function movemarked($urlParams, $form) {
296 if($_REQUEST['DestFolderID'] && is_numeric($_REQUEST['DestFolderID'])) {
297 $destFolderID = $_REQUEST['DestFolderID'];
298 $fileList = "'" . ereg_replace(' *, *',"','",trim(addslashes($_REQUEST['FileIDs']))) . "'";
301 if($fileList != "''") {
302 $files = DataObject
::get("File", "`File`.ID IN ($fileList)");
304 foreach($files as $file) {
305 if($file instanceof Image
) {
306 $file->deleteFormattedImages();
308 $file->ParentID
= $destFolderID;
313 user_error("No files in $fileList could be found!", E_USER_ERROR
);
317 $message = 'Moved '.$numFiles.' files';
318 FormResponse
::status_message($message, "good");
319 FormResponse
::add("$('Form_EditForm').getPageFromServer($('Form_EditForm_ID').value)");
320 return FormResponse
::respond();
322 user_error("Bad data: $_REQUEST[DestFolderID]", E_USER_ERROR
);
327 * Perform the "delete marked" action.
328 * Called and returns in same way as 'save' function
330 public function deletemarked($urlParams, $form) {
331 $fileList = "'" . ereg_replace(' *, *',"','",trim(addslashes($_REQUEST['FileIDs']))) . "'";
335 if($fileList != "''") {
336 $files = DataObject
::get("File", "ID IN ($fileList)");
338 foreach($files as $file) {
339 if($file instanceof Image
) {
340 $file->deleteFormattedImages();
343 $folderID = $file->ParentID
;
345 // $deleteList .= "\$('Form_EditForm_Files').removeById($file->ID);\n";
349 if($brokenPages = Notifications
::getItems("BrokenLink")) {
350 $brokenPageList = " These pages now have broken links:</ul>";
351 foreach($brokenPages as $brokenPage) {
352 $brokenPageList .= "<li style="font-size: 65%">" . $brokenPage->Breadcrumbs(3, true) . "</li>";
354 $brokenPageList .= "</ul>";
355 Notifications
::notifyByEmail("BrokenLink", "Page_BrokenLinkEmail");
357 $brokenPageList = '';
362 $remaining = DB
::query("SELECT COUNT(*) FROM `File` WHERE `ParentID`=$folderID")->value();
365 $deleteList .= "Element.removeClassName(\$('sitetree').getTreeNodeByIdx( '$folderID' ).getElementsByTagName('a')[0],'contents');";
369 user_error("No files in $fileList could be found!", E_USER_ERROR
);
372 $message = "Deleted $numFiles files.$brokenPageList";
373 FormResponse
::add($deleteList);
374 FormResponse
::status_message($message, "good");
375 FormResponse
::add("$('Form_EditForm').getPageFromServer($('Form_EditForm_ID').value)");
376 return FormResponse
::respond();
381 * Returns the content to be placed in Form_SubForm when editing a file.
384 public function getfile() {
385 SSViewer
::setOption('rewriteHashlinks', false);
387 // bdc: only try to return something if user clicked on an object
388 if (is_object($this->getSubForm($this->urlParams
['ID']))) {
389 return $this->getSubForm($this->urlParams
['ID'])->formHtmlContent();
395 * Action handler for the save button on the file subform.
398 public function savefile($data, $form) {
399 $record = DataObject
::get_by_id("File", $data['ID']);
400 $form->saveInto($record);
402 $title = Convert
::raw2js($record->Title
);
403 $name = Convert
::raw2js($record->Name
);
405 statusMessage('Saved file #$data[ID]');
406 $('record-$data[ID]').getElementsByTagName('td')[1].innerHTML = "$title";
407 $('record-$data[ID]').getElementsByTagName('td')[2].innerHTML = "$name";
412 * Return the entire site tree as a nested set of ULs
415 public function SiteTreeAsUL() {
416 $obj = singleton('Folder');
417 $obj->setMarkingFilter("ClassName", "Folder");
418 $obj->markPartialTree();
420 if($p = $this->currentPage()) $obj->markToExpose($p);
422 // getChildrenAsUL is a flexible and complex way of traversing the tree
424 $siteTree = $obj->getChildrenAsUL("",
426 ' "<li id=\"record-$child->ID\" class=\"$child->class" . $child->markingClasses() . ($extraArg->isCurrentPage($child) ? " current" : "") . "\">" . ' .
428 ' "<a href=\"" . Director::link(substr($extraArg->Link(),0,-1), "show", $child->ID) . "\" class=\"" . ($child->hasChildren() ? " contents" : "") . "\" >" . $child->Title . "</a>" ',
433 // Wrap the root if needs be.
435 $rootLink = $this->Link() . 'show/root';
437 if(!isset($rootID)) $siteTree = "<ul id=\"sitetree\" class=\"tree unformatted\"><li id=\"record-root\" class=\"Root\"><a href=\"$rootLink\">http://www.yoursite.com/assets</a>"
439 . $siteTree . "</li></ul>";
447 * Returns a subtree of items underneat the given folder.
449 public function getsubtree() {
450 $obj = DataObject
::get_by_id("Folder", $_REQUEST['ID']);
451 $obj->setMarkingFilter("ClassName", "Folder");
452 $obj->markPartialTree();
454 $results = $obj->getChildrenAsUL("",
456 ' "<li id=\"record-$child->ID\" class=\"$child->class" . $child->markingClasses() . ($extraArg->isCurrentPage($child) ? " current" : "") . "\">" . ' .
458 ' "<a href=\"" . Director::link(substr($extraArg->Link(),0,-1), "show", $child->ID) . "\" >" . $child->Title . "</a>" ',
462 return substr(trim($results), 4,-5);
467 //------------------------------------------------------------------------------------------//
469 // Data saving handlers
472 * Add a new folder and return its details suitable for ajax.
474 public function addfolder() {
475 $parent = ($_REQUEST['ParentID'] && is_numeric($_REQUEST['ParentID'])) ?
$_REQUEST['ParentID'] : 0;
478 $parentObj = DataObject
::get_by_id("File", $parent);
479 if(!$parentObj ||
!$parentObj->ID
) $parent = 0;
483 $p->ParentID
= $parent;
484 $p->Title
= "NewFolder";
486 $p->Name
= "NewFolder";
488 // Get the folder to be created
489 if(isset($parentObj->ID
)) $filename = $parentObj->FullPath
. $p->Name
;
490 else $filename = '../assets/' . $p->Name
;
494 $baseFilename = $filename . '-';
495 while(file_exists($filename)) {
496 $filename = $baseFilename . $i;
497 $p->Name
= $p->Title
= basename($filename);
503 chmod($filename, 02775);
508 return $this->returnItemToUser($p);
513 * Return the given tree item to the client.
514 * If called by ajax, this will be some javascript commands.
515 * Otherwise, it will redirect back.
517 public function returnItemToUser($p) {
518 if($_REQUEST['ajax']) {
519 $parentID = (int)$p->ParentID
;
521 tree = $('sitetree');
523 var newNode = tree.createTreeNode($p->ID, "$p->Title", "$p->class");
525 tree.getTreeNodeByIdx($parentID).appendTreeNode(newNode);
527 newNode.selectTreeNode();
532 Director
::redirectBack();
540 public function deletefolder() {
542 $ids = split(' *, *', $_REQUEST['csvIDs']);
544 foreach($ids as $id) {
546 if(is_numeric($id)) {
548 $record = DataObject
::get_by_id($this->stat('tree_class'), $id);
552 Debug
::message( "Record appears to be null" );
556 /*if($record->hasMethod('BackLinkTracking')) {
557 $brokenPages = $record->BackLinkTracking();
559 foreach($brokenPages as $brokenPage) {
561 $brokenPageList .= "<li style=\"font-size: 65%\">" . $brokenPage->Breadcrumbs(3, true) . "</li>";
563 $brokenPage->HasBrokenLink = true;
565 $notifications[$brokenPage->OwnerID][] = $brokenPage;
567 $brokenPage->write();
578 // DataObject::delete_by_id($this->stat('tree_class'), $id);
580 $script .= $this->deleteTreeNodeJS($record);
588 /*if($notifications) foreach($notifications as $memberID => $pages) {
590 $email = new Page_BrokenLinkEmail();
592 $email->populateTemplate(new ArrayData(array(
594 "Recipient" => DataObject::get_by_id("Member", $memberID),
596 "BrokenPages" => new DataObjectSet($pages),
608 $s = (sizeof($ids) > 1) ?
"s" :"";
610 $message = sizeof($ids) . " folder$s deleted.";
612 if(isset($brokenPageList)) $message .= " The following pages now have broken links:<ul>" . addslashes($brokenPageList) . "</ul>Their owners have been emailed and they will fix up those pages.";
613 $script .= "statusMessage('$message');";
617 public function removefile(){
618 if($fileID = $this->urlParams
['ID']){
619 $file = DataObject
::get_by_id('File', $fileID);
620 // Delete the temp verions of this file in assets/_resampled
621 if($file instanceof Image
) {
622 $file->deleteFormattedImages();
627 if(Director
::is_ajax()) {
629 $('Form_EditForm_Files').removeFile($fileID);
630 statusMessage('removed file', 'good');
633 Director
::redirectBack();
636 user_error("AssetAdmin::removefile: Bad parameters: File=$fileID", E_USER_ERROR
);
640 public function save($urlParams, $form) {
641 $form->dataFieldByName('Title')->value
= $form->dataFieldByName('Name')->value
;
643 return parent
::save($urlParams, $form);