/*
* Copyright 2013 Anyware Services
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* This UI helper provides a dialog box to upload a file from hard drive
* See #open method.
*/
Ext.define('Ametys.helper.FileUpload', {
singleton: true,
/**
* @property {Function} _cbFn The call back function to call after choosing file
* @private
*/
/**
* @property {Function} _autovalidate Should automatically validate after a file is chosen?
* @property {String} _autovalidate.filename The filename
* @property {String} _autovalidate.return True to autovalidate. False otherwise
* @private
*/
/**
* @property _filter {String} The file name filter
* @private
*/
/**
* @property _box {Ametys.window.DialogBox} The dialog box
* @private
*/
/**
* Allow the user to choose a resource file from its local hard drive
* @param {Object} config The configuration options:
* @param {String} [config.icon] The full path to icon (16x16) for the dialog box. If null the iconCls will be used
* @param {String} [config.iconCls=ametysicon-upload119] One or more CSS classes to apply to dialog's icon. Can be null to use the default one.
* @param {String} config.title The title of the dialog box.
* @param {String} config.helpmessage The message displayed at the top of the dialog box.
* @param {Function} config.callback The method that will be called when the dialog box is closed. The method signature is <ul><li>node : The tree node currently selected or null if no selection has been made (cancel)</li></ul> The method can return false to made the dialog box keep open (you should display an error message in this case)
* @param {Function} config.autovalidate Should automatically validate the dialog after the given file is chosen?
* @param {String} config.autovalidate.filename The chosen filename
* @param {Boolean} config.autovalidate.return True to autovalidate the dialog. False otherwise
* @param {Function/String[]} [config.filter] The filter for the filename. Choose between constants. Argument is the file name and return a boolean. OR can be a list of extensions.
* @param {String[]} [config.allowedExtensions] The allowed extensions for the filename. Can be null. If use the filter function will be forced to Ametys.helper.FileUpload#EXTENSIONS_FILTER
* @param {String[]} [config.uploadUrl] The serveur URL to call for upload. Defaults to [CMS_URL]/plugins/core/upload/store
*/
open: function (config)
{
this._cbFn = config.callback;
this._autovalidate = config.autovalidate || function(filename) { return false };
this._filter = config.filter;
this._allowedExtensions = config.allowedExtensions;
if (Ext.isArray(this._filter))
{
this._allowedExtensions = this._filter;
}
if (this._allowedExtensions)
{
// Force filter
this._filter = Ametys.helper.FileUpload.EXTENSION_FILTER;
}
this._uploadUrl = config.uploadUrl || Ametys.getPluginDirectPrefix('core') + "/upload/store";
this._initialize(config.icon, config.iconCls, config.title, config.helpmessage);
this._box.show();
},
/**
* Initialize the dialog box
* @param {String} icon The icon path. Can be null if you set an iconCls.
* @param {String} iconCls One or more CSS classes to apply to dialog's icon. Will not be used if an icon path is set
* @param {String} title The title of the dialog box.
* @param {String} helpmessage The message displayed at the top of the dialog box.
* @private
*/
_initialize: function(icon, iconCls, title, helpmessage)
{
if (!this._initialized)
{
this._box = Ext.create('Ametys.window.DialogBox', {
width: 430,
scrollable: true,
layout: 'fit',
items: [{
xtype: 'form',
border: false,
defaults: {
cls: 'ametys',
labelAlign: 'top',
labelSeparator: '',
width: '100%'
},
items: [{
xtype: 'component',
cls: 'a-text',
itemId: 'fileupload-help',
html: ''
},
{
xtype: 'filefield',
name: 'file',
id: 'fileupload-file',
emptyText: "{{i18n PLUGINS_CORE_UI_FILEUPLOAD_FIELD_EMPTYTEXT}}",
fieldLabel: "{{i18n PLUGINS_CORE_UI_FILEUPLOAD_FIELD_FIELDLABEL}}",
buttonText: "{{i18n PLUGINS_CORE_UI_FILEUPLOAD_FIELD_BUTTONTEXT}}",
listeners: {'change': {fn: this._selectFile, scope: this}}
}
]
}
],
defaultFocus: 'fileupload-file',
closeAction: 'hide',
buttons : [{
text :"{{i18n PLUGINS_CORE_UI_FILEUPLOAD_BOX_OK}}",
itemId: 'fileupload-ok',
disabled: true,
handler : Ext.bind(this._submit, this)
}, {
text :"{{i18n PLUGINS_CORE_UI_FILEUPLOAD_BOX_CANCEL}}",
itemId: 'fileupload-cancel',
handler: Ext.bind(function() {this._box.hide();}, this)
}],
listeners: {
'show': function() {
var me = this;
me._box.down('#fileupload-file').fileInputEl.dom.setAttribute("accept", this._allowedExtensions ? this._allowedExtensions.map(s => "." + s).join(",") : "")
window.setTimeout(function() {
me._box.down('#fileupload-file').fileInputEl.dom.click();
}, 1); // Timeout required so listeners are correctly attached
},
scope: this
}
});
this._initialized = true;
}
this._box.down('#fileupload-file').reset();
this._box.queryById('fileupload-ok').setDisabled(true);
if (icon)
{
this._box.setIcon(icon);
this._box.setIconCls(null);
}
else
{
this._box.setIconCls(iconCls || 'ametysicon-upload119');
this._box.setIcon(null);
}
this._box.setTitle(title);
this._box.queryById('fileupload-help').update(helpmessage);
},
/**
* @property {String[]} IMAGE_FILTER Filter for image files
* @readonly
*/
IMAGE_FILTER: ['jpg', 'jpeg', 'gif', 'png', 'svg'],
/**
* @property {String} IMAGE_FILTER_LABEL Label for image filter
* @readonly
*/
IMAGE_FILTER_LABEL: "{{i18n PLUGINS_CORE_UI_FILEUPLOAD_IMAGEFILTER}}",
/**
* @property {String[]} VIDEO_FILTER Filter for videos files
* @readonly
*/
VIDEO_FILTER: ['ogv','mp4','webm','avi','mkv','mov','mpeg'],
/**
* @property {String} VIDEO_FILTER_LABEL Label for video filter
* @readonly
*/
VIDEO_FILTER_LABEL: "{{i18n PLUGINS_CORE_UI_FILEUPLOAD_VIDEOFILTER}}",
/**
* @property {String[]} MULTIMEDIA_FILTER Filter for videos and audio files
* @readonly
*/
MULTIMEDIA_FILTER: ['swf','flv','ogv','mp4','webm','avi','mkv','mov','mpg','mpeg','mp3'],
/**
* @property {String} MULTIMEDIA_FILTER_LABEL Label for multimedia filter
* @readonly
*/
MULTIMEDIA_FILTER_LABEL: "{{i18n PLUGINS_CORE_UI_FILEUPLOAD_MULTIMEDIAFILTER}}",
/**
* @property {String[]} SOUND_FILTER Filter for audio files
* @readonly
*/
SOUND_FILTER: ['mp3','oga','wav'],
/**
* @property {String} SOUND_FILTER_LABEL Label for sound filter
* @readonly
*/
SOUND_FILTER_LABEL: "{{i18n PLUGINS_CORE_UI_FILEUPLOAD_SOUNDFILTER}}",
/**
* @property {String[]} FLASH_FILTER Filter for SWF files
* @readonly
*/
FLASH_FILTER: ['swf'],
/**
* @property {String} FLASH_FILTER_LABEL Label for flash filter
* @readonly
*/
FLASH_FILTER_LABEL: "{{i18n PLUGINS_CORE_UI_FILEUPLOAD_FLASHFILTER}}",
/**
* @property {String[]} JSON_FILTER Filter for Json files
* @readonly
*/
JSON_FILTER: ['json'],
/**
* @property {String} JSON_FILTER_LABEL Label for Json filter
* @readonly
*/
JSON_FILTER_LABEL: "{{i18n PLUGINS_CORE_UI_FILEUPLOAD_JSONFILTER}}",
/**
* @property {String[]} PDF_FILTER Filter for PDF files
* @readonly
*/
PDF_FILTER: ['pdf'],
/**
* @property {String} PDF_FILTER_LABEL Label for PDF filter
* @readonly
*/
PDF_FILTER_LABEL: "{{i18n PLUGINS_CORE_UI_FILEUPLOAD_PDFFILTER}}",
/**
* Filter function to filter files based on the allowed extensions
* @param {String} filename The file name
* @return {Boolean} true if the filter match
*/
EXTENSION_FILTER: function (filename)
{
if (!this._allowedExtensions)
{
return true;
}
for (var i = 0; i < this._allowedExtensions.length; i++)
{
if (new RegExp('\\.' + this._allowedExtensions[i] + '$', "i").test(filename))
{
return true;
}
}
return false;
},
/**
* @property {String} EXTENSION_FILTER_LABEL Label for filter
* @readonly
*/
EXTENSION_FILTER_LABEL: "{{i18n PLUGINS_CORE_UI_FILEUPLOAD_EXTENSIONFILTER}}",
/**
* Function called when the file input change
* @param {Ext.form.field.File} input The file input
* @param {String} name The file name
* @private
*/
_selectFile: function (input, name)
{
// _uploading boolean is used to prevent to execution of this code twice, because for some reasons, in IE, the
// change event is raised again when the form is submitted (with an empty value)
if (!this._uploading)
{
var matchFilter = this._filter == null || this._filter(name);
this._box.queryById('fileupload-ok').setDisabled(!matchFilter);
if (!matchFilter)
{
var text = "{{i18n PLUGINS_CORE_UI_FILEUPLOAD_ERRORDIALOG_DESC}} " + Ametys.getObjectByName("Ametys.helper.FileUpload." + this._filter.$name + '_LABEL');
if (this._allowedExtensions)
{
text += this._getAllowedExtensionsAsText();
}
Ametys.log.ErrorDialog.display({
title: "{{i18n PLUGINS_CORE_UI_FILEUPLOAD_ERRORDIALOG_TEXT}}",
text: text,
details: "",
category: "Ametys.helper.FileUpload._selectFile"
});
}
else if (this._autovalidate(name))
{
this._box.down('#fileupload-ok').click();
}
}
},
/**
* Get the allowed extension as a text to be displayed for user
* @return {String} the allowed extension as a text
*/
_getAllowedExtensionsAsText: function ()
{
if (this._allowedExtensions.length > 2)
{
var slicedExtensions = Ext.Array.slice(this._allowedExtensions, 0, this._allowedExtensions.length -1);
var txt = slicedExtensions.join("{{i18n PLUGINS_CORE_UI_FILEUPLOAD_EXTENSIONFILTER_SEPARATOR}}");
txt += "{{i18n PLUGINS_CORE_UI_FILEUPLOAD_EXTENSIONFILTER_SEPARATOR_FINAL}}";
txt += this._allowedExtensions[this._allowedExtensions.length -1];
return txt;
}
else
{
return this._allowedExtensions.join("{{i18n PLUGINS_CORE_UI_FILEUPLOAD_EXTENSIONFILTER_SEPARATOR_FINAL}}");
}
},
/**
* This function submits the form to upload the selected file
* @protected
*/
_submit: function()
{
this._uploading = true;
this._box.down('form').getForm().submit({
url : this._uploadUrl,
waitTitle: "{{i18n PLUGINS_CORE_UI_FILEUPLOAD_SUBMITFORM_TITLE}}",
waitMsg: "{{i18n PLUGINS_CORE_UI_FILEUPLOAD_SUBMITFORM_MSG}}",
success: Ext.bind(this._submitSuccess, this),
failure: Ext.bind(this._submitFailure, this)
});
},
/**
* The function to call when {@link #_submit} succeeded.
* @param {Ext.form.Basic} form The form that requested the submit action
* @param {Ext.form.action.Action} action The Action class.
* @protected
*/
_submitSuccess: function (form, action)
{
this._uploading = false;
if (this._cbFn)
{
Ext.Function.defer (this._cbFn, 0, null, [action.result.id, action.result.filename || action.result.name, action.result.size, Ametys.CONTEXT_PATH + action.result.viewHref, Ametys.CONTEXT_PATH + action.result.downloadHref, action.result]);
}
this._box.hide();
},
/**
* The function to call when {@link #_submit} failed.
* @param {Ext.form.Basic} form The form that requested the submit action
* @param {Ext.form.action.Action} action The Action class.
* @protected
*/
_submitFailure: function (form, action)
{
this._uploading = false;
this._box.queryById('fileupload-ok').setDisabled(true);
if (action.result.error == "rejected")
{
Ametys.log.ErrorDialog.display({
title: "{{i18n PLUGINS_CORE_UI_FILEUPLOAD_ERROR_MSG}}",
text: "{{i18n PLUGINS_CORE_UI_FILEUPLOAD_ERROR_FILEREJECTED}}",
details: "",
category: "Ametys.helper.FileUpload"
});
}
else if (action.result.error == "infected")
{
Ametys.log.ErrorDialog.display({
title: "{{i18n PLUGINS_CORE_UI_FILEUPLOAD_ERROR_MSG}}",
text: "{{i18n PLUGINS_CORE_UI_FILEUPLOAD_ERROR_FILE_INFECTED}}",
details: "",
category: "Ametys.helper.FileUpload"
});
}
else
{
Ametys.log.ErrorDialog.display({
title: "{{i18n PLUGINS_CORE_UI_FILEUPLOAD_ERROR_MSG}}",
text: "{{i18n PLUGINS_CORE_UI_FILEUPLOAD_ERROR_ON_SERVER}}",
details: action.result.error ? action.result.error.message + "\n" + action.result.error.stacktrace : "",
category: "Ametys.helper.FileUpload"
});
}
}
});