1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Synacor, Inc. 5 * 6 * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at: https://www.zimbra.com/license 9 * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15 10 * have been added to cover use of software over a computer network and provide for limited attribution 11 * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B. 12 * 13 * Software distributed under the License is distributed on an "AS IS" basis, 14 * WITHOUT WARRANTY OF ANY KIND, either express or implied. 15 * See the License for the specific language governing rights and limitations under the License. 16 * The Original Code is Zimbra Open Source Web Client. 17 * The Initial Developer of the Original Code is Zimbra, Inc. All rights to the Original Code were 18 * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015. 19 * 20 * All portions of the code are Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 /** 25 * @overview 26 */ 27 28 /** 29 * Creates an upload dialog. 30 * @class 31 * This class represents an upload dialog. 32 * 33 * @param {DwtComposite} shell the parent 34 * @param {String} className the class name 35 * 36 * @extends DwtDialog 37 */ 38 ZmUploadDialog = function(shell, className) { 39 className = className || "ZmUploadDialog"; 40 var title = ZmMsg.uploadDocs; 41 DwtDialog.call(this, {parent:shell, className:className, title:title}); 42 this.setButtonListener(DwtDialog.OK_BUTTON, new AjxListener(this, this._upload)); 43 this._createUploadHtml(); 44 this._showLinkTitleText = false; 45 this._linkText = {}; 46 this._controller = null; 47 48 this._inprogress = false; 49 } 50 51 ZmUploadDialog.prototype = new DwtDialog; 52 ZmUploadDialog.prototype.constructor = ZmUploadDialog; 53 54 // Constants 55 56 ZmUploadDialog.UPLOAD_FIELD_NAME = "uploadFile"; 57 ZmUploadDialog.UPLOAD_TITLE_FIELD_NAME = "uploadFileTitle"; 58 59 // Data 60 61 ZmUploadDialog.prototype._selector; 62 63 ZmUploadDialog.prototype._uploadFolder; 64 ZmUploadDialog.prototype._uploadCallback; 65 66 ZmUploadDialog.prototype._extensions; 67 68 // Public methods 69 /** 70 * Enables the link title option. 71 * 72 * @param {Boolean} enabled if <code>true</code>, to enbled the link title option 73 */ 74 ZmUploadDialog.prototype.enableLinkTitleOption = 75 function(enabled) { 76 this._showLinkTitleText = enabled; 77 }; 78 79 /** 80 * Sets allowed extensions. 81 * 82 * @param {Array} array an array of extensions 83 */ 84 ZmUploadDialog.prototype.setAllowedExtensions = 85 function(array) { 86 this._extensions = array; 87 if (array) { 88 for (var i = 0; i < array.length; i++) { 89 array[i] = array[i].toUpperCase(); 90 } 91 } 92 }; 93 94 ZmUploadDialog.prototype.getNotes = 95 function(){ 96 return (this._notes ? this._notes.value : ""); 97 }; 98 99 ZmUploadDialog.prototype.setNotes = 100 function(notes){ 101 if(this._notes){ 102 this._notes.value = (notes || ""); 103 } 104 }; 105 106 ZmUploadDialog.prototype.warnInProgress = function() { 107 var msgDlg = appCtxt.getMsgDialog(); 108 msgDlg.setMessage(ZmMsg.uploadDisabledInProgress, DwtMessageDialog.WARNING_STYLE); 109 msgDlg.popup(); 110 } 111 112 ZmUploadDialog.prototype.popup = 113 function(controller, folder, callback, title, loc, oneFileOnly, noResolveAction, showNotes, isImage, conflictAction) { 114 if (this._inprogress) { 115 this.warnInProgress(); 116 return; 117 } 118 119 this._controller = controller; 120 this._uploadFolder = folder; 121 this._uploadCallback = callback; 122 this._conflictAction = conflictAction; 123 var aCtxt = ZmAppCtxt.handleWindowOpener(); 124 125 this._supportsHTML5 = AjxEnv.supportsHTML5File && !this._showLinkTitleText && (aCtxt.get(ZmSetting.DOCUMENT_SIZE_LIMIT) != null); 126 127 this.setTitle(title || ZmMsg.uploadDocs); 128 129 // reset input fields 130 var table = this._tableEl; 131 var rows = table.rows; 132 while (rows.length) { 133 table.deleteRow(rows.length - 1); 134 } 135 this._addFileInputRow(oneFileOnly); 136 137 // hide/show elements 138 var id = this._htmlElId; 139 var labelEl = document.getElementById(id+"_label"); 140 if (labelEl) { 141 if(oneFileOnly && isImage){ 142 labelEl.innerHTML = ZmMsg.uploadChooseImage; 143 Dwt.setVisible(labelEl, true); 144 } 145 else{ 146 labelEl.innerHTML = ZmMsg.uploadChoose; 147 Dwt.setVisible(labelEl, !oneFileOnly); 148 } 149 } 150 var actionRowEl = document.getElementById(id+"_actionRow"); 151 if (actionRowEl) { 152 Dwt.setVisible(actionRowEl, !noResolveAction); 153 } 154 155 var notesEl = document.getElementById(id+"_notesTD"); 156 if (notesEl) { 157 Dwt.setVisible(notesEl, showNotes); 158 } 159 // In case of a single file upload show proper info message 160 161 var docSizeInfo = document.getElementById((id+"_info")); 162 var attSize = AjxUtil.formatSize(aCtxt.get(ZmSetting.DOCUMENT_SIZE_LIMIT) || 0, true); 163 if(docSizeInfo){ 164 if(oneFileOnly){ 165 docSizeInfo.innerHTML = AjxMessageFormat.format(ZmMsg.attachmentLimitMsgSingleFile, attSize); 166 } 167 else{ 168 docSizeInfo.innerHTML = AjxMessageFormat.format(ZmMsg.attachmentLimitMsg, attSize); 169 } 170 } 171 172 173 // show 174 DwtDialog.prototype.popup.call(this, loc); 175 }; 176 177 ZmUploadDialog.prototype.popdown = 178 function() { 179 /*** 180 // NOTE: Do NOT set these values to null! The conflict dialog will 181 // call back to this dialog after it's hidden to process the 182 // files that should be replaced. 183 this._uploadFolder = null; 184 this._uploadCallback = null; 185 /***/ 186 187 this._extensions = null; 188 189 //Cleanup 190 this._enableStatus = false; 191 192 this._notes.removeAttribute("disabled"); 193 this.setNotes(""); 194 this._msgInfo.innerHTML = ""; 195 this._conflictAction = null; 196 197 DwtDialog.prototype.popdown.call(this); 198 }; 199 200 ZmUploadDialog.prototype._popupErrorDialog = function(message) { 201 var dialog = appCtxt.getMsgDialog(); 202 dialog.setMessage(message, DwtMessageDialog.CRITICAL_STYLE, this._title); 203 dialog.popup(); 204 }; 205 206 //to give explicitly the uploadForm, files to upload and folderId used for briefcase 207 ZmUploadDialog.prototype.uploadFiles = 208 function(files, uploadForm, folder) { 209 if (this._inprogress) { 210 this.warnInProgress(); 211 return; 212 } 213 214 if (files.length == 0) { 215 return; 216 } 217 this._uploadFolder = folder; 218 219 var popDownCallback = this.popdown.bind(this); 220 var uploadParams = { 221 uploadFolder: folder, 222 preResolveConflictCallback: popDownCallback, 223 errorCallback: popDownCallback, 224 finalCallback: this._finishUpload.bind(this), 225 docFiles: files 226 } 227 228 var aCtxt = appCtxt.isChildWindow ? parentAppCtxt : appCtxt; 229 var briefcaseApp = aCtxt.getApp(ZmApp.BRIEFCASE); 230 var callback = briefcaseApp.uploadSaveDocs.bind(briefcaseApp, null, uploadParams); 231 232 var uploadMgr = appCtxt.getUploadManager(); 233 window._uploadManager = uploadMgr; 234 235 try { 236 uploadMgr.execute(callback, uploadForm); 237 } catch (ex) { 238 if (ex.msg) { 239 this._popupErrorDialog(ex.msg); 240 } else { 241 this._popupErrorDialog(ZmMsg.unknownError); 242 } 243 } 244 }; 245 246 // Protected methods 247 ZmUploadDialog.prototype._upload = function(){ 248 var form = this._uploadForm; 249 var uploadFiles = []; 250 var errors = {}; 251 this._linkText = {}; 252 var aCtxt = ZmAppCtxt.handleWindowOpener(); 253 var maxSize = aCtxt.get(ZmSetting.DOCUMENT_SIZE_LIMIT); 254 var elements = form.elements; 255 var notes = this.getNotes(); 256 var fileObj = []; 257 var zmUploadManager = appCtxt.getZmUploadManager(); 258 var file; 259 var msgFormat; 260 var errorFilenames; 261 var newError; 262 for (var i = 0; i < elements.length; i++) { 263 var element = form.elements[i]; 264 if ((element.name != ZmUploadDialog.UPLOAD_FIELD_NAME) || !element.value) continue; 265 266 this._msgInfo.innerHTML = ""; 267 var errors = []; 268 if(this._supportsHTML5){ 269 var files = element.files; 270 var errors = []; 271 for (var j = 0; j < files.length; j++){ 272 file = files[j]; 273 fileObj.push(file); 274 newError = zmUploadManager.getErrors(file, maxSize); 275 if (newError) { 276 errors.push(newError); 277 } else { 278 uploadFiles.push({name: file.name, fullname: file.name, notes: notes}); 279 } 280 } 281 } else { 282 var fileName = element.value.replace(/^.*[\\\/:]/, ""); 283 file = { name: fileName }; 284 newError = zmUploadManager.getErrors(file, maxSize); 285 if (newError) { 286 errors.push(newError); 287 } else { 288 uploadFiles.push({ fullname: element.value, name: fileName, notes: notes}); 289 } 290 } 291 if(this._showLinkTitleText) { 292 var id = element.id; 293 id = id.replace("_input", "") + "_titleinput"; 294 var txtElement = document.getElementById(id); 295 if(txtElement) { 296 this._linkText[file.name] = txtElement.value; 297 } 298 } 299 300 } 301 302 if (errors.length > 0) { 303 this._msgInfo.innerHTML = zmUploadManager.createUploadErrorMsg(errors, maxSize, "<br>"); 304 } else if (uploadFiles.length > 0) { 305 var briefcaseApp = aCtxt.getApp(ZmApp.BRIEFCASE); 306 var shutDownCallback = null; 307 var uploadButton = null; 308 if (this._controller == null) { 309 shutDownCallback = this.popdown.bind(this); 310 } else { 311 var toolbar = this._controller.getCurrentToolbar(); 312 if (toolbar) { 313 uploadButton = toolbar.getOp(ZmOperation.NEW_FILE); 314 } 315 this.popdown(); 316 shutDownCallback = this._enableUpload.bind(this, uploadButton); 317 } 318 var uploadParams = { 319 attachment: false, 320 files: fileObj, 321 notes: notes, 322 allResponses: null, 323 start: 0, 324 uploadFolder: this._uploadFolder, 325 completeAllCallback: briefcaseApp.uploadSaveDocs.bind(briefcaseApp), 326 conflictAction: this._conflictAction || this._selector.getValue(), 327 preResolveConflictCallback: shutDownCallback, 328 errorCallback: shutDownCallback, 329 completeDocSaveCallback: this._finishUpload.bind(this, uploadButton), 330 docFiles: uploadFiles 331 } 332 uploadParams.progressCallback = this._uploadFileProgress.bind(this, uploadButton, uploadParams); 333 334 try { 335 if (this._supportsHTML5) { 336 zmUploadManager.upload(uploadParams); 337 } else { 338 var callback = briefcaseApp.uploadSaveDocs.bind(briefcaseApp, null, uploadParams); 339 var uploadMgr = appCtxt.getUploadManager(); 340 window._uploadManager = uploadMgr; 341 uploadMgr.execute(callback, this._uploadForm); 342 } 343 344 if (uploadButton) { 345 // The 16x16 upload image has ImgUpload0 (no fill) .. ImgUpload12 (completely filled) variants to give a 346 // gross idea of the progress. 347 ZmToolBar._setButtonStyle(uploadButton, null, ZmMsg.uploading, "Upload0"); 348 this._inprogress = true; 349 } 350 } catch (ex) { 351 this._enableUpload(uploadButton); 352 if (ex.msg) { 353 this._popupErrorDialog(ex.msg); 354 } else { 355 this._popupErrorDialog(ZmMsg.unknownError); 356 } 357 } 358 } 359 }; 360 361 ZmUploadDialog.prototype._uploadFileProgress = 362 function(uploadButton, params, progress) { 363 if (!uploadButton || !params || !progress.lengthComputable || !params.totalSize) return; 364 365 // The 16x16 upload image has ImgUpload0 (no fill) .. ImgUpload12 (completely filled) variants to give a 366 // gross idea of the progress. A tooltip indicating the progress will be added too. 367 var progressFraction = (progress.loaded / progress.total); 368 var uploadedSize = params.uploadedSize + (params.currentFileSize * progressFraction); 369 var fractionUploaded = uploadedSize/params.totalSize; 370 if (fractionUploaded > 1) { 371 fractionUploaded = 1; 372 } 373 var progressBucket = Math.round(fractionUploaded * 12); 374 DBG.println(AjxDebug.DBG3,"Upload Progress: total=" + params.totalSize + ", uploadedSize=" + params.uploadedSize + 375 ", currentSize="+ params.currentFileSize + ", progressFraction=" + 376 progressFraction + ", fractionUploaded=" + fractionUploaded + ", progressBucket=" + progressBucket); 377 ZmToolBar._setButtonStyle(uploadButton, null, ZmMsg.uploadNewFile, "Upload" + progressBucket.toString()); 378 379 var tooltip = AjxMessageFormat.format(ZmMsg.uploadPercentComplete, [ Math.round(fractionUploaded * 100) ] ) 380 uploadButton.setToolTipContent(tooltip, true); 381 }; 382 383 ZmUploadDialog.prototype._enableUpload = function(uploadButton) { 384 if (!uploadButton) return; 385 386 ZmToolBar._setButtonStyle(uploadButton, null, ZmMsg.uploadNewFile, null); 387 uploadButton.setToolTipContent(ZmMsg.uploadNewFile, true); 388 this._inprogress = false; 389 }; 390 391 ZmUploadDialog.prototype._finishUpload = function(uploadButton, docFiles, uploadFolder) { 392 var filenames = []; 393 for (var i in docFiles) { 394 var name = docFiles[i].name; 395 if(this._linkText[name]) { 396 docFiles[i].linkText = this._linkText[name]; 397 } 398 filenames.push(name); 399 } 400 this._enableUpload(uploadButton); 401 402 this._uploadCallback.run(uploadFolder, filenames, docFiles); 403 }; 404 405 ZmUploadDialog.prototype._addFileInputRow = function(oneInputOnly) { 406 var id = Dwt.getNextId(); 407 var inputId = id + "_input"; 408 var removeId = id + "_remove"; 409 var addId = id + "_add"; 410 var sizeId = id + "_size"; 411 412 var table = this._tableEl; 413 var row = table.insertRow(-1); 414 415 var cellLabel = row.insertCell(-1); 416 cellLabel.innerHTML = ZmMsg.fileLabel; 417 418 var cell = row.insertCell(-1); 419 // bug:53841 allow only one file upload when oneInputOnly is set 420 cell.innerHTML = [ 421 "<input id='",inputId,"' type='file' name='",ZmUploadDialog.UPLOAD_FIELD_NAME,"' size=30 ",(this._supportsHTML5 ? (oneInputOnly ? "" : "multiple") : ""),">" 422 ].join(""); 423 424 var cell = row.insertCell(-1); 425 cell.id = sizeId; 426 cell.innerHTML = " "; 427 428 //HTML5 429 if(this._supportsHTML5){ 430 var inputEl = document.getElementById(inputId); 431 var sizeEl = cell; 432 Dwt.setHandler(inputEl, "onchange", AjxCallback.simpleClosure(this._handleFileSize, this, inputEl, sizeEl)); 433 } 434 435 if(oneInputOnly){ 436 cell.colSpan = 3; 437 }else{ 438 var cell = row.insertCell(-1); 439 cell.innerHTML = [ 440 "<span ", 441 "id='",removeId,"' ", 442 "onmouseover='this.style.cursor=\"pointer\"' ", 443 "onmouseout='this.style.cursor=\"default\"' ", 444 "style='color:blue;text-decoration:underline;'", 445 ">", ZmMsg.remove, "</span>" 446 ].join(""); 447 var removeSpan = document.getElementById(removeId); 448 Dwt.setHandler(removeSpan, DwtEvent.ONCLICK, ZmUploadDialog._removeHandler); 449 450 var cell = row.insertCell(-1); 451 cell.innerHTML = " "; 452 var cell = row.insertCell(-1); 453 cell.innerHTML = [ 454 "<span ", 455 "id='",addId,"' ", 456 "onmouseover='this.style.cursor=\"pointer\"' ", 457 "onmouseout='this.style.cursor=\"default\"' ", 458 "style='color:blue;text-decoration:underline;'", 459 ">", ZmMsg.add, "</span>" 460 ].join(""); 461 var addSpan = document.getElementById(addId); 462 Dwt.setHandler(addSpan, DwtEvent.ONCLICK, ZmUploadDialog._addHandler); 463 } 464 465 466 if(this._showLinkTitleText) { 467 var txtInputId = id + "_titleinput"; 468 var txtRow = table.insertRow(-1); 469 var txtCell = txtRow.insertCell(-1); 470 txtCell.innerHTML = [ 471 ZmMsg.linkTitleOptionalLabel 472 ].join(""); 473 474 txtCell = txtRow.insertCell(-1); 475 txtCell.innerHTML = [ 476 "<input id='",txtInputId,"' type='text' name='",ZmUploadDialog.UPLOAD_TITLE_FIELD_NAME,"' size=40>" 477 ].join(""); 478 txtCell.colSpan = 3; 479 } 480 }; 481 482 ZmUploadDialog.prototype._handleFileSize = 483 function(inputEl, sizeEl){ 484 485 var files = inputEl.files; 486 if(!files) return; 487 488 var sizeStr = [], className, totalSize =0; 489 for(var i=0; i<files.length;i++){ 490 var file = files[i]; 491 var size = file.size || file.fileSize /*Safari*/ || 0; 492 var aCtxt = ZmAppCtxt.handleWindowOpener(); 493 if(size > aCtxt.get(ZmSetting.DOCUMENT_SIZE_LIMIT)) 494 className = "RedC"; 495 totalSize += size; 496 } 497 498 if(sizeEl) { 499 sizeEl.innerHTML = " ("+AjxUtil.formatSize(totalSize, true)+")"; 500 if(className) 501 Dwt.addClass(sizeEl, "RedC"); 502 else 503 Dwt.delClass(sizeEl, "RedC"); 504 } 505 506 }; 507 508 ZmUploadDialog._removeHandler = function(event) { 509 var span = DwtUiEvent.getTarget(event || window.event); 510 var cell = span.parentNode; 511 var row = cell.parentNode; 512 513 var endRow = row; 514 515 if(span.id) { 516 var id = span.id; 517 id = id.replace("_remove", "") + "_titleinput"; 518 var txtInput = document.getElementById(id); 519 if(txtInput) { 520 var txtCell = txtInput.parentNode; 521 var txtRow = txtCell.parentNode; 522 endRow = txtRow; 523 } 524 } 525 526 if (row.previousSibling == null && endRow.nextSibling == null) { 527 var comp = DwtControl.findControl(span); 528 comp._addFileInputRow(); 529 } 530 531 if(endRow != row) { 532 endRow.parentNode.removeChild(endRow); 533 } 534 535 row.parentNode.removeChild(row); 536 }; 537 538 ZmUploadDialog._addHandler = function(event) { 539 var span = DwtUiEvent.getTarget(event || window.event); 540 var comp = DwtControl.findControl(span); 541 comp._addFileInputRow(); 542 }; 543 544 ZmUploadDialog.prototype._createUploadHtml = function() { 545 var id = this._htmlElId; 546 var aCtxt = ZmAppCtxt.handleWindowOpener(); 547 var uri = aCtxt.get(ZmSetting.CSFE_UPLOAD_URI); 548 549 var subs = { 550 id: id, 551 uri: uri 552 }; 553 this.setContent(AjxTemplate.expand("share.Dialogs#UploadDialog", subs)); 554 555 //variables 556 this._uploadForm = document.getElementById((id+"_form")); 557 this._tableEl = document.getElementById((id + "_table")); 558 this._msgInfo = document.getElementById((id+"_msg")); 559 this._notes = document.getElementById((id+"_notes")); 560 561 //Conflict Selector 562 this._selector = new DwtSelect({parent:this}); 563 this._selector.addOption(ZmMsg.uploadActionKeepMine, false, ZmBriefcaseApp.ACTION_KEEP_MINE); 564 this._selector.addOption(ZmMsg.uploadActionKeepTheirs, false, ZmBriefcaseApp.ACTION_KEEP_THEIRS); 565 this._selector.addOption(ZmMsg.uploadActionAsk, true, ZmBriefcaseApp.ACTION_ASK); 566 this._selector.reparentHtmlElement((id+"_conflict")); 567 568 //Info Section 569 var docSizeInfo = document.getElementById((id+"_info")); 570 if(docSizeInfo){ 571 var attSize = AjxUtil.formatSize(aCtxt.get(ZmSetting.DOCUMENT_SIZE_LIMIT) || 0, true) 572 docSizeInfo.innerHTML = AjxMessageFormat.format(ZmMsg.attachmentLimitMsg, attSize); 573 } 574 575 }; 576