From 9a62f7799bb3dd654057af2f49b7e399d914b3b6 Mon Sep 17 00:00:00 2001 From: Marina Glancy Date: Thu, 10 May 2012 16:00:46 +0800 Subject: [PATCH] MDL-32867 Working with external references in filemanager - Files that are references to external resources have special shortcut icon in filemanager - When user selects a REF file in filemanager, he can see the 'Original' of the file in the way that original repository wants to show it, it is loaded dynamically via AJAX request - Files that are themselves the source of references of other files in the system have 'link' icon in filemanager. When user tries to remove/rename/overwrite SRC file he is warned that all ## existing references will be updated/converted to copies. - Changed confirmation messages for deleting, moving/renaming of the folders - confirmation dialog in filemanager is using YUI3 now --- files/renderer.php | 43 ++++++++++--- lang/en/repository.php | 5 ++ lib/filelib.php | 3 + lib/form/filemanager.js | 103 +++++++++++++++++++++++++++++-- repository/draftfiles_ajax.php | 14 +++++ theme/base/style/filemanager.css | 8 +++ 6 files changed, 163 insertions(+), 13 deletions(-) diff --git a/files/renderer.php b/files/renderer.php index 5098b354ebc61..769db563e14fe 100644 --- a/files/renderer.php +++ b/files/renderer.php @@ -105,7 +105,10 @@ public function render_form_filemanager($fm) { 'strings' => array( array('error', 'moodle'), array('info', 'moodle'), array('confirmdeletefile', 'repository'), array('draftareanofiles', 'repository'), array('entername', 'repository'), array('enternewname', 'repository'), - array('invalidjson', 'repository'), array('popupblockeddownload', 'repository') + array('invalidjson', 'repository'), array('popupblockeddownload', 'repository'), + array('unknownoriginal', 'repository'), array('confirmdeletefolder', 'repository'), + array('confirmdeletefilewithhref', 'repository'), array('confirmrenamefolder', 'repository'), + array('confirmrenamefile', 'repository') ) ); if (empty($filemanagertemplateloaded)) { @@ -234,7 +237,10 @@ private function fm_print_generallayout($fm) { private function fm_js_template_iconfilename() { $rv = '
+
+
+
'.$this->pix_icon('i/menu', '▶').'
'; @@ -311,6 +317,9 @@ private function fm_js_template_message() { * is unavailable. If there is information available, the content of embedded element * with class 'fp-value' will be substituted with the value; * + * The value of Original ('fp-original') is loaded in separate request. When it is applicable + * but not yet loaded the 'fp-original' element receives additional class 'fp-loading'; + * * Elements with classes 'fp-file-update', 'fp-file-download', 'fp-file-delete', 'fp-file-zip', * 'fp-file-unzip', 'fp-file-setmain' and 'fp-file-cancel' will hold corresponding onclick * events (there may be several elements with class 'fp-file-cancel'); @@ -326,6 +335,8 @@ private function fm_js_template_message() { * @return string */ private function fm_js_template_fileselectlayout() { + $strloading = get_string('loading', 'repository'); + $icon_progress = $this->pix_icon('i/loading_small', $strloading).''; $rv = '
@@ -343,7 +354,7 @@ private function fm_js_template_fileselectlayout() { : : - +'.$icon_progress.' '.$strloading.'

@@ -362,6 +373,25 @@ private function fm_js_template_fileselectlayout() { return preg_replace('/\{\!\}/', '', $rv); } + /** + * FileManager JS template for popup confirm dialogue window. + * + * Must have one top element, CSS for this element must define width and height of the window; + * + * content of element with class 'fp-dlg-text' will be replaced with dialog text; + * elements with classes 'fp-dlg-butconfirm' and 'fp-dlg-butcancel' will + * hold onclick events; + * + * @return string + */ + private function fm_js_template_confirmdialog() { + $rv = '

+
+
+
'; + return preg_replace('/\{\!\}/', '', $rv); + } + /** * Returns all FileManager JavaScript templates as an array. * @@ -686,7 +716,7 @@ private function fp_js_template_error() { /** * FilePicker JS template for error/info message displayed as a separate popup window. * - * Must be wrapped in an element with class 'fp-msg', CSS for this element must define + * Must be wrapped in one element, CSS for this element must define * width and height of the window. It will be assigned with an additional class 'fp-msg-error' * or 'fp-msg-info' depending on message type; * @@ -697,7 +727,7 @@ private function fp_js_template_error() { * @return string */ private function fp_js_template_message() { - $rv = '
+ $rv = '
'; @@ -707,8 +737,7 @@ private function fp_js_template_message() { /** * FilePicker JS template for popup dialogue window asking for action when file with the same name already exists. * - * Must be wrapped in an element with class 'fp-dlg', CSS for this element must define width - * and height of the window; + * Must have one top element, CSS for this element must define width and height of the window; * * content of element with class 'fp-dlg-text' will be replaced with dialog text; * elements with classes 'fp-dlg-butoverwrite', 'fp-dlg-butrename' and 'fp-dlg-butcancel' will @@ -720,7 +749,7 @@ private function fp_js_template_message() { * @return string */ private function fp_js_template_processexistingfile() { - $rv = '
+ $rv = '
diff --git a/lang/en/repository.php b/lang/en/repository.php index 63e5cae89cbff..82a6b7b0bc10b 100644 --- a/lang/en/repository.php +++ b/lang/en/repository.php @@ -63,7 +63,11 @@ $string['configsaved'] = 'Configuration saved!'; $string['confirmdelete'] = 'Are you sure you want to delete this repository - {$a}? If you choose "Continue and download", file references to external contents will be downloaded to moodle, but it could take long time to process.'; $string['confirmdeletefile'] = 'Are you sure you want to delete this file?'; +$string['confirmrenamefile'] = 'Are you sure you want to rename/move this file? There are {$a} alias/shortcut files that use this file as their source. If you proceed then those aliases will be converted to true copies.'; +$string['confirmdeletefilewithhref'] = 'Are you sure you want to delete this file? There are {$a} alias/shortcut files that use this file as their source. If you proceed then those aliases will be converted to true copies.'; +$string['confirmdeletefolder'] = 'Are you sure you want to delete this folder? All files and subfolders will be deleted.'; $string['confirmremove'] = 'Are you sure you want to remove this repository plugin, its options and all of its instances - {$a}? If you choose "Continue and download", file references to external contents will be downloaded to moodle, but it could take long time to process.'; +$string['confirmrenamefolder'] = ' Are you sure you want to move/rename this folder? Any alias/shortcut files that reference files in this folder will be converted into true copies.'; $string['continueuninstall'] = 'Continue'; $string['continueuninstallanddownload'] = 'Continue and download'; $string['copying'] = 'Copying'; @@ -191,6 +195,7 @@ $string['title'] = 'Choose a file...'; $string['type'] = 'Type'; $string['typenotvisible'] = 'Type not visible'; +$string['unknownoriginal'] = 'Unknown'; $string['upload'] = 'Upload this file'; $string['uploading'] = 'Uploading...'; $string['uploadsucc'] = 'The file has been uploaded successfully'; diff --git a/lib/filelib.php b/lib/filelib.php index 29307f9e57dd3..0dc39a3fa5341 100644 --- a/lib/filelib.php +++ b/lib/filelib.php @@ -597,7 +597,10 @@ function file_get_drafarea_files($draftitemid, $filepath = '/') { $item->license = $file->get_license(); $item->datemodified = $file->get_timemodified(); $item->datecreated = $file->get_timecreated(); + $item->isref = $file->is_external_file(); + $item->refcount = $fs->get_reference_count($file); + // TODO MDL-32900 this is not the correct way to check that it is archive, use filetype_parser instead if ($icon == 'zip') { $item->type = 'zip'; } else { diff --git a/lib/form/filemanager.js b/lib/form/filemanager.js index ec191321a7609..453fc76fb7f26 100644 --- a/lib/form/filemanager.js +++ b/lib/form/filemanager.js @@ -491,6 +491,12 @@ M.form_filemanager.init = function(Y, options) { if (node.filename || node.filepath || (node.path && node.path != '/')) { classname = classname + ' fp-hascontextmenu'; } + if (node.isref) { + classname = classname + ' fp-isreference'; + } + if (node.refcount) { + classname = classname + ' fp-hasreferences'; + } if (node.sortorder == 1) { classname = classname + ' fp-mainfile';} return Y.Lang.trim(classname); } @@ -554,7 +560,7 @@ M.form_filemanager.init = function(Y, options) { set('value', list[i]).setContent(list[i])) } }, - update_file: function() { + update_file: function(confirmed) { var selectnode = this.selectnode; var fileinfo = this.selectui.fileinfo; @@ -572,12 +578,18 @@ M.form_filemanager.init = function(Y, options) { var licensechanged = (newlicense != fileinfo.license); var params, action; + var dialog_options = {callback:this.update_file, callbackargs:[true], scope:this}; if (fileinfo.type == 'folder') { if (!newfilename) { this.print_msg(M.str.repository.entername, 'error'); return; } if (filenamechanged || filepathchanged) { + if (!confirmed) { + dialog_options.message = M.str.repository.confirmrenamefolder; + this.show_confirm_dialog(dialog_options); + return; + } params = {filepath:fileinfo.filepath, newdirname:newfilename, newfilepath:targetpath}; action = 'updatedir'; } @@ -586,6 +598,11 @@ M.form_filemanager.init = function(Y, options) { this.print_msg(M.str.repository.enternewname, 'error'); return; } + if ((filenamechanged || filepathchanged) && !confirmed && fileinfo.refcount) { + dialog_options.message = M.util.get_string('confirmrenamefile', 'repository', fileinfo.refcount); + this.show_confirm_dialog(dialog_options); + return; + } if (filenamechanged || filepathchanged || licensechanged || authorchanged) { params = {filepath:fileinfo.filepath, filename:fileinfo.fullname, newfilename:newfilename, newfilepath:targetpath, @@ -617,6 +634,50 @@ M.form_filemanager.init = function(Y, options) { } }); }, + /** + * Displays a confirmation dialog + * Expected attributes in dialog_options: message, callback, callbackargs(optional), scope(optional) + */ + show_confirm_dialog: function(dialog_options) { + // instead of M.util.show_confirm_dialog(e, dialog_options); + if (!this.confirm_dlg) { + this.confirm_dlg_node = Y.Node.create(M.form_filemanager.templates.confirmdialog); + var node = this.confirm_dlg_node; + node.generateID(); + Y.one(document.body).appendChild(node); + this.confirm_dlg = new Y.Panel({ + srcNode : node, + zIndex : 800000, + centered : true, + modal : true, + visible : false, + render : true, + buttons : {} + }); + this.confirm_dlg.plug(Y.Plugin.Drag,{handles:['#'+node.get('id')+' .yui3-widget-hd']}); + var handleConfirm = function(ev) { + var dlgopt = this.confirm_dlg.dlgopt; + ev.preventDefault(); + this.confirm_dlg.hide(); + if (dlgopt.callback) { + if (dlgopt.callbackargs) { + dlgopt.callback.apply(dlgopt.scope || this, dlgopt.callbackargs); + } else { + dlgopt.callback.apply(dlgopt.scope || this); + } + } + } + var handleCancel = function(ev) { + ev.preventDefault(); + this.confirm_dlg.hide(); + } + node.one('.fp-dlg-butconfirm').on('click', handleConfirm, this); + node.one('.fp-dlg-butcancel').on('click', handleCancel, this); + } + this.confirm_dlg.dlgopt = dialog_options; + this.confirm_dlg_node.one('.fp-dlg-text').setContent(dialog_options.message); + this.confirm_dlg.show(); + }, setup_select_file: function() { var selectnode = this.selectnode; // bind labels with corresponding inputs @@ -639,13 +700,19 @@ M.form_filemanager.init = function(Y, options) { e.preventDefault(); var dialog_options = {}; var params = {}; - dialog_options.message = M.str.repository.confirmdeletefile; + var fileinfo = this.selectui.fileinfo; dialog_options.scope = this; - if (this.selectui.fileinfo.type == 'folder') { + params.filepath = fileinfo.filepath; + if (fileinfo.type == 'folder') { params.filename = '.'; - params.filepath = this.selectui.fileinfo.filepath; + dialog_options.message = M.str.repository.confirmdeletefolder; } else { - params.filename = this.selectui.fileinfo.fullname; + params.filename = fileinfo.fullname; + if (fileinfo.refcount) { + dialog_options.message = M.util.get_string('confirmdeletefilewithhref', 'repository', fileinfo.refcount); + } else { + dialog_options.message = M.str.repository.confirmdeletefile; + } } dialog_options.callbackargs = [params]; dialog_options.callback = function(params) { @@ -665,7 +732,7 @@ M.form_filemanager.init = function(Y, options) { }); }; this.selectui.hide(); // TODO remove this after confirm dialog is replaced with YUI3 - M.util.show_confirm_dialog(e, dialog_options); + this.show_confirm_dialog(dialog_options); }, this); selectnode.one('.fp-file-zip').on('click', function(e) { e.preventDefault(); @@ -778,6 +845,30 @@ M.form_filemanager.init = function(Y, options) { setStyle('maxHeight', ''+(node.thumbnail_height ? node.thumbnail_height : 90)+'px'). setStyle('maxWidth', ''+(node.thumbnail_width ? node.thumbnail_width : 90)+'px'); selectnode.one('.fp-thumbnail').setContent('').appendChild(imgnode); + // load original location if applicable + if (node.isref && !node.original) { + selectnode.one('.fp-original').removeClass('fp-unknown').addClass('fp-loading'); + this.request({ + action: 'getoriginal', + scope: this, + params: {'filepath':node.filepath,'filename':node.fullname}, + callback: function(id, obj, args) { + // check if we did not select another file yet + var scope = args.scope; + if (scope.selectui.fileinfo && node && + scope.selectui.fileinfo.filepath == node.filepath && + scope.selectui.fileinfo.fullname == node.fullname) { + selectnode.one('.fp-original').removeClass('fp-loading'); + if (obj.original) { + node.original = obj.original; + selectnode.one('.fp-original .fp-value').setContent(node.original); + } else { + selectnode.one('.fp-original .fp-value').setContent(M.str.repository.unknownsource); + } + } + } + }, false); + } // show panel this.selectui.show(); }, diff --git a/repository/draftfiles_ajax.php b/repository/draftfiles_ajax.php index 2dcfe500753a3..afe39ded7cbb8 100644 --- a/repository/draftfiles_ajax.php +++ b/repository/draftfiles_ajax.php @@ -383,6 +383,20 @@ } die; + case 'getoriginal': + $filename = required_param('filename', PARAM_FILE); + $filepath = required_param('filepath', PARAM_PATH); + + $fs = get_file_storage(); + $file = $fs->get_file($user_context->id, 'user', 'draft', $draftid, $filepath, $filename); + if (!$file) { + echo json_encode(false); + } else { + $return = array('filename' => $filename, 'filepath' => $filepath, 'original' => $file->get_reference_details()); + echo json_encode((object)$return); + } + die; + default: // no/unknown action? echo json_encode(false); diff --git a/theme/base/style/filemanager.css b/theme/base/style/filemanager.css index a156bf4eff326..559f39aa044dd 100644 --- a/theme/base/style/filemanager.css +++ b/theme/base/style/filemanager.css @@ -262,6 +262,14 @@ background: #CCC!important;filter: progid:DXImageTransform.Microsoft.gradient(st .filemanager.fp-select.fp-cansetmain .fp-file-setmain {display:inline-block;} .filemanager.fp-select.fp-folder .fp-file-download {display:none;} /* to be implemented */ +.filemanager .fp-iconview .fp-reficons {position:absolute;height:100%;width:100%;top:0;left:0;z-index:1000;} +.filemanager .fp-iconview .fp-file.fp-hasreferences .fp-reficons {background: url([[pix:moodle|t/lock]]) no-repeat;background-position:bottom left;} +.filemanager .fp-iconview .fp-file.fp-isreference .fp-reficons {background: url([[pix:moodle|t/right]]) no-repeat;background-position:bottom right;} + +.filemanager.fp-select .fp-original.fp-unknown {display:none;} +.filemanager.fp-select .fp-original .fp-originloading {display:none;} +.filemanager.fp-select .fp-original.fp-loading .fp-originloading {display:inline;} + /* * Drag and drop support */