MDL-32867 Working with external references in filemanager
authorMarina Glancy <marina@moodle.com>
Thu, 10 May 2012 08:00:46 +0000 (16:00 +0800)
committerMarina Glancy <marina@moodle.com>
Mon, 21 May 2012 03:57:53 +0000 (11:57 +0800)
- 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
lang/en/repository.php
lib/filelib.php
lib/form/filemanager.js
repository/draftfiles_ajax.php
theme/base/style/filemanager.css

index 5098b35..769db56 100644 (file)
@@ -105,7 +105,10 @@ class core_files_renderer extends plugin_renderer_base {
             '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 @@ class core_files_renderer extends plugin_renderer_base {
     private function fm_js_template_iconfilename() {
         $rv = '<div class="fp-file" style="position:relative">
         <a href="#">
+    <div style="position:relative;">
     <div class="{!}fp-thumbnail"></div>
+    <div class="fp-reficons"></div>
+    </div>
     <div class="{!}fp-filename"></div></a>
     <a class="{!}fp-contextmenu" href="#">'.$this->pix_icon('i/menu', '▶').'</a>
 </div>';
@@ -311,6 +317,9 @@ class core_files_renderer extends plugin_renderer_base {
      * 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 @@ class core_files_renderer extends plugin_renderer_base {
      * @return string
      */
     private function fm_js_template_fileselectlayout() {
+        $strloading  = get_string('loading', 'repository');
+        $icon_progress = $this->pix_icon('i/loading_small', $strloading).'';
         $rv = '<div class="filemanager fp-select">
 <div class="fp-select-loading">
 <img src="'.$this->pix_url('i/loading').'" />
@@ -343,7 +354,7 @@ class core_files_renderer extends plugin_renderer_base {
 <tr class="{!}fp-path"><td class="mdl-right"><label>'.get_string('path', 'moodle').'</label>:</td>
 <td class="mdl-left"><select></select></td></tr>
 <tr class="{!}fp-original"><td class="mdl-right"><label>'.get_string('original', 'repository').'</label>:</td>
-<td class="mdl-left"><span class="fp-value"/></td></tr>
+<td class="mdl-left"><span class="fp-originloading">'.$icon_progress.' '.$strloading.'</span><span class="fp-value"/></td></tr>
 </table>
 <p><button class="{!}fp-file-update" >'.get_string('update', 'moodle').'</button>
 <button class="{!}fp-file-download" >'.get_string('download').'</button>
@@ -362,6 +373,25 @@ class core_files_renderer extends plugin_renderer_base {
         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 = '<div class="fp-dlg"><div class="{!}fp-dlg-text"></div>
+<div class="fp-dlg-but"><button class="{!}fp-dlg-butconfirm" >'.get_string('ok').'</button></div>
+<div class="fp-dlg-but"><button class="{!}fp-dlg-butcancel" >'.get_string('cancel').'</button></div>
+</div>';
+        return preg_replace('/\{\!\}/', '', $rv);
+    }
+
     /**
      * Returns all FileManager JavaScript templates as an array.
      *
@@ -686,7 +716,7 @@ class core_files_renderer extends plugin_renderer_base {
     /**
      * 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 @@ class core_files_renderer extends plugin_renderer_base {
      * @return string
      */
     private function fp_js_template_message() {
-        $rv = '<div class="{!}fp-msg">
+        $rv = '<div class="fp-msg">
                     <div class="{!}fp-msg-text"></div>
                     <div><button class="{!}fp-msg-butok">'.get_string('ok').'</button></div>
                 </div>';
@@ -707,8 +737,7 @@ class core_files_renderer extends plugin_renderer_base {
     /**
      * 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 @@ class core_files_renderer extends plugin_renderer_base {
      * @return string
      */
     private function fp_js_template_processexistingfile() {
-        $rv = '<div class="{!}fp-dlg"><div class="{!}fp-dlg-text"></div>
+        $rv = '<div class="fp-dlg"><div class="{!}fp-dlg-text"></div>
 <div class="fp-dlg-but"><button class="{!}fp-dlg-butoverwrite" >'.get_string('overwrite', 'repository').'</button></div>
 <div class="fp-dlg-but"><button class="{!}fp-dlg-butrename" /></div>
 <div class="fp-dlg-but"><button class="{!}fp-dlg-butcancel" >'.get_string('cancel').'</button></div>
index 63e5cae..82a6b7b 100644 (file)
@@ -63,7 +63,11 @@ $string['configcacheexpire'] = 'The amount of time that file listings are cached
 $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 <strong>all of its instances</strong> - {$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['thumbview'] = 'View as icons';
 $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';
index 29307f9..0dc39a3 100644 (file)
@@ -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 {
index ec19132..453fc76 100644 (file)
@@ -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();
         },
index 2dcfe50..afe39de 100644 (file)
@@ -383,6 +383,20 @@ switch ($action) {
         }
         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);
index a156bf4..559f39a 100644 (file)
@@ -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
  */