1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 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) 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 a dialog containing a listview of items that were (hard) deleted by
 30  * the user.
 31  * @class
 32  * This class represents a dumpster dialog.
 33  *
 34  * @param	{DwtComposite}	parent		the parent
 35  * @param	{String}	className		the class name
 36  *
 37  * @extends		ZmDialog
 38  */
 39 ZmDumpsterDialog = function(parent, className) {
 40 
 41 	var params = {
 42 		parent: parent,
 43 		className: (className || "ZmDumpsterDialog"),
 44 		title: ZmMsg.recoverDeletedItems,
 45 		standardButtons: [DwtDialog.CANCEL_BUTTON]
 46 	};
 47 	ZmDialog.call(this, params);
 48 
 49 	this.getButton(DwtDialog.CANCEL_BUTTON).setText(ZmMsg.close);
 50 
 51 	this._controller = new ZmDumpsterListController(this);
 52 };
 53 
 54 ZmDumpsterDialog.prototype = new ZmDialog;
 55 ZmDumpsterDialog.prototype.constructor = ZmDumpsterDialog;
 56 
 57 ZmDumpsterDialog.prototype.toString =
 58 function() {
 59 	return "ZmDumpsterDialog";
 60 };
 61 
 62 ZmDumpsterDialog.prototype.popup =
 63 function(searchFor, types) {
 64 	this._searchTypes = types ? AjxUtil.toArray(types) : [ZmItem.MSG];
 65 	this._searchFor = searchFor;
 66 	this.runSearchQuery("");
 67 
 68 	ZmDialog.prototype.popup.call(this);
 69 };
 70 
 71 
 72 ZmDumpsterDialog.prototype.runSearchQuery =
 73 function(query) {
 74 	var types = this._searchTypes;
 75 	var searchFor = this._searchFor;
 76 	var params = {
 77 		query: "-in:/Junk " + query, // Users don't want/need to recover deleted spam.
 78 		searchFor: searchFor,
 79 		types: types,
 80 		sortBy: ZmSearch.DATE_DESC,
 81 		noRender: true,
 82 		inDumpster: true,
 83 		skipUpdateSearchToolbar: true, //don't update the main app search toolbar. Otherwise the main app is updated to weird stuff like the mixed view of everything.
 84 		callback: this._controller.show.bind(this._controller, [types])
 85 	};
 86 	this._controller.show(types, null); // Clear the list & set headers
 87 	appCtxt.getSearchController().search(params);
 88 
 89 };
 90 
 91 ZmDumpsterDialog.prototype.popdown =
 92 function() {
 93 	ZmDialog.prototype.popdown.call(this);
 94 	if (this._inputField) {
 95 		this._inputField.clear(); //clear for next time
 96 	}
 97 
 98 	this._controller.cleanup();
 99 };
100 
101 ZmDumpsterDialog.prototype._contentHtml =
102 function() {
103 	this._inputContainerId = this._htmlElId + "_inputContainerId";
104 	this._searchButtonContainerId = this._htmlElId + "_searchButtonContainerId";
105 	return AjxTemplate.expand("share.Widgets#ZmDumpsterDialog", {id:this._htmlElId});
106 };
107 
108 ZmDumpsterDialog.prototype._listSelectionListener =
109 function(ev) {
110 	var sel = this._listview.getSelection() || [];
111 	this._toolbar.enableAll((sel.length > 0));
112 };
113 
114 ZmDumpsterDialog.prototype._handleInputFieldKeyDown =
115 function(ev) {
116 	if (ev.keyCode == 13 || ev.keyCode == 3) {
117 		this._controller._searchListener();
118 	}
119 };
120 
121 
122 
123 ZmDumpsterDialog.prototype._resetTabFocus =
124 function(){
125 	this._tabGroup.setFocusMember(this._inputField, true);
126 };
127 
128 /**
129  * adds non-standard elements to tab group.
130  */
131 ZmDumpsterDialog.prototype._updateTabGroup =
132 function() {
133 	this._tabGroup.addMember(this._inputField);
134 	this._tabGroup.addMember(this._searchButton);
135 };
136 
137 ZmDumpsterDialog.prototype._initializeSearchBar =
138 function(listener) {
139 
140 	this._inputField = new DwtInputField({parent: this});
141 	this._inputField.addListener(DwtEvent.ONKEYUP, this._handleInputFieldKeyDown.bind(this));//this._controller._searchListener.bind(this._controller));
142 
143 	document.getElementById(this._inputContainerId).appendChild(this._inputField.getHtmlElement());
144 
145 	var el = document.getElementById(this._searchButtonContainerId);
146 	var params = {parent:this, parentElement:el, id: "searchDumpsterButton"};
147 
148 	var button = this._searchButton = new DwtButton(params);
149 	el.style.paddingLeft="4px";
150 
151 	button.setText(ZmMsg.search);
152 	button.addSelectionListener(listener);
153 };
154 
155 ZmDumpsterDialog.prototype.getSearchText =
156 function() {
157 	return this._inputField.getValue();
158 };
159 
160 
161 /**
162  * Listview showing deleted items
163  *
164  * @param parent
165  */
166 ZmDumpsterListView = function(parent, controller) {
167 	if (!arguments.length) return;
168 	this._controller = controller;
169 	var params = {
170 		parent: parent,
171 		controller: controller,
172 		pageless: true,
173 		view: this._getViewId(),
174 		headerList: this._getHeaderList(),
175 		type: this._getType(),
176 		parentElement: (parent._htmlElId + "_listview")
177 	};
178 	this._type = this._getType();
179 
180 	ZmListView.call(this, params);
181 
182 	this._listChangeListener = new AjxListener(this, this._changeListener);
183 };
184 
185 ZmDumpsterListView.prototype = new ZmListView;
186 ZmDumpsterListView.prototype.constructor = ZmDumpsterListView;
187 
188 ZmDumpsterListView.prototype.toString =
189 function() {
190 	return "ZmDumpsterListView";
191 };
192 ZmDumpsterListView.prototype._getViewId =
193 function() {
194 	var type = this._getType();
195 	var appName = ZmItem.APP[type];
196 	return "dumpster" + appName;
197 };
198 
199 ZmDumpsterListView.prototype._getCellId =
200 function(item, field) {
201 	return this._getFieldId(item, field);
202 };
203 
204 ZmDumpsterListView.prototype._getType =
205 function() {
206 	throw "ZmDumpsterListView.prototype._getType must be overridden by all inheriting classes";
207 };
208 
209 ZmDumpsterListView.createView = function(view, parent, controller) {
210 	var app = view.replace(/^dumpster/,"");
211 	switch (app) {
212 		case ZmApp.MAIL:
213 			return new ZmDumpsterMailListView(parent, controller);
214 		case ZmApp.CONTACTS:
215 			return new ZmDumpsterContactListView(parent, controller);
216 		case ZmApp.CALENDAR:
217 			return new ZmDumpsterCalendarListView(parent, controller);
218 		case ZmApp.TASKS:
219 			return new ZmDumpsterTaskListView(parent, controller);
220 		case ZmApp.BRIEFCASE:
221 			return new ZmDumpsterBriefcaseListView(parent, controller);
222 	}
223 };
224 
225 
226 /**
227  * Listview showing deleted mail items
228  *
229  * @param parent
230  * @param controller
231  */
232 ZmDumpsterMailListView = function(parent, controller) {
233 	ZmDumpsterListView.call(this, parent, controller);
234 };
235 
236 ZmDumpsterMailListView.prototype = new ZmDumpsterListView;
237 ZmDumpsterMailListView.prototype.constructor = ZmDumpsterMailListView;
238 ZmDumpsterMailListView.prototype.toString =
239 function() {
240 	return "ZmDumpsterMailListView";
241 };
242 
243 ZmDumpsterMailListView.prototype._getHeaderList =
244 function() {
245 	return [
246 		(new DwtListHeaderItem({field:ZmItem.F_FROM, text:ZmMsg.from, width:ZmMsg.COLUMN_WIDTH_FROM_MLV})),
247 		(new DwtListHeaderItem({field:ZmItem.F_SUBJECT, text:ZmMsg.subject})),
248 		(new DwtListHeaderItem({field:ZmItem.F_DATE, text:ZmMsg.received, width:ZmMsg.COLUMN_WIDTH_DATE}))
249 	];
250 };
251 
252 ZmDumpsterMailListView.prototype._getType =
253 function() {
254 	return ZmItem.MSG;
255 };
256 
257 ZmDumpsterMailListView.prototype._getCellContents =
258 function(htmlArr, idx, item, field, colIdx, params) {
259 	var content;
260 	if (field == ZmItem.F_FROM) {
261 		var fromAddr = item.getAddress(AjxEmailAddress.FROM);
262 		if (fromAddr) {
263 			content = "<span style='white-space:nowrap'>";
264 			var name = fromAddr.getName() || fromAddr.getDispName() || fromAddr.getAddress();
265 			content += AjxStringUtil.htmlEncode(name);
266 			content += "</span>";
267 		}
268 	}
269 	else if (field == ZmItem.F_SUBJECT) {
270 		var subj = item.subject || ZmMsg.noSubject;
271 	
272 		content = AjxStringUtil.htmlEncode(subj);
273 	}
274 	if (content) {
275 		htmlArr[idx++] = content;
276 	} else {
277 		idx = ZmListView.prototype._getCellContents.apply(this, arguments);
278 	}
279 	return idx;
280 };
281 
282 ZmDumpsterMailListView.prototype._getToolTip =
283 function(params) {
284 	return AjxStringUtil.htmlEncode(params.item.getFragment());
285 };
286 
287 
288 /**
289  * Listview showing deleted contact items
290  *
291  * @param parent
292  * @param controller
293  */
294 ZmDumpsterContactListView = function(parent, controller) {
295 	ZmDumpsterListView.call(this, parent, controller);
296 };
297 
298 ZmDumpsterContactListView.prototype = new ZmDumpsterListView;
299 ZmDumpsterContactListView.prototype.constructor = ZmDumpsterContactListView;
300 ZmDumpsterContactListView.prototype.toString =
301 function() {
302 	return "ZmDumpsterContactListView";
303 };
304 
305 ZmDumpsterContactListView.prototype._getHeaderList =
306 function() {
307 	return [
308 		(new DwtListHeaderItem({field:ZmItem.F_NAME, text:ZmMsg.name})),
309 		(new DwtListHeaderItem({field:ZmItem.F_EMAIL, text:ZmMsg.email, width: 200}))
310 	];
311 };
312 
313 ZmDumpsterContactListView.prototype._getType =
314 function() {
315 	return ZmItem.CONTACT;
316 };
317 
318 ZmDumpsterContactListView.prototype._getCellContents =
319 function(htmlArr, idx, item, field, colIdx, params) {
320 	var content;
321 	if (field == ZmItem.F_NAME) {
322 		var name = ZmContact.computeFileAs(item);
323 		content = AjxStringUtil.htmlEncode(name);
324 	}
325 	else if (field == ZmItem.F_EMAIL) {
326 		var email = item.getEmail();
327 		content = email && AjxStringUtil.htmlEncode(email) || " ";
328 	}
329 	if (content) {
330 		htmlArr[idx++] = content;
331 	} else {
332 		idx = ZmListView.prototype._getCellContents.apply(this, arguments);
333 	}
334 	return idx;
335 };
336 
337 
338 /**
339  * Listview showing deleted calendar items
340  *
341  * @param parent
342  * @param controller
343  */
344 ZmDumpsterCalendarListView = function(parent, controller) {
345 	ZmDumpsterListView.call(this, parent, controller);
346 };
347 
348 ZmDumpsterCalendarListView.prototype = new ZmDumpsterListView;
349 ZmDumpsterCalendarListView.prototype.constructor = ZmDumpsterCalendarListView;
350 ZmDumpsterCalendarListView.prototype.toString =
351 function() {
352 	return "ZmDumpsterCalendarListView";
353 };
354 
355 ZmDumpsterCalendarListView.prototype._getHeaderList =
356 function() {
357 	return [
358 		(new DwtListHeaderItem({field:ZmItem.F_SUBJECT, text:ZmMsg.subject})),
359 		(new DwtListHeaderItem({field:ZmItem.F_DATE, text:ZmMsg.date, width:ZmMsg.COLUMN_WIDTH_DATE_CAL}))
360 	];
361 };
362 
363 ZmDumpsterCalendarListView.prototype._getType =
364 function() {
365 	return ZmItem.APPT;
366 };
367 
368 ZmDumpsterCalendarListView.prototype._getCellContents =
369 function(htmlArr, idx, item, field, colIdx, params) {
370 	var content;
371 	if (field == ZmItem.F_SUBJECT) {
372 		var subj = item.name;
373 		content = AjxStringUtil.htmlEncode(subj);
374 	}
375 	else if (field == ZmItem.F_DATE) {
376 		content = item.startDate != null
377 			? AjxDateUtil.simpleComputeDateStr(item.startDate)
378 			: " ";
379 	}
380 	if (content) {
381 		htmlArr[idx++] = content;
382 	} else {
383 		idx = ZmListView.prototype._getCellContents.apply(this, arguments);
384 	}
385 	return idx;
386 };
387 
388 ZmDumpsterCalendarListView.prototype._getToolTip =
389 function(params) {
390 	return params.item.fragment;
391 };
392 
393 
394 /**
395  * Listview showing deleted task items
396  *
397  * @param parent
398  * @param controller
399  */
400 ZmDumpsterTaskListView = function(parent, controller) {
401 	ZmDumpsterListView.call(this, parent, controller);
402 };
403 
404 ZmDumpsterTaskListView.prototype = new ZmDumpsterListView;
405 ZmDumpsterTaskListView.prototype.constructor = ZmDumpsterTaskListView;
406 ZmDumpsterTaskListView.prototype.toString =
407 function() {
408 	return "ZmDumpsterTaskListView";
409 };
410 
411 ZmDumpsterTaskListView.prototype._getHeaderList =
412 function() {
413 	return [
414 		(new DwtListHeaderItem({field:ZmItem.F_SUBJECT, text:ZmMsg.subject})),
415 		(new DwtListHeaderItem({field:ZmItem.F_STATUS, text:ZmMsg.status, width:ZmMsg.COLUMN_WIDTH_STATUS_TLV})),
416 		(new DwtListHeaderItem({field:ZmItem.F_DATE, text:ZmMsg.date, width:ZmMsg.COLUMN_WIDTH_DATE_DUE_TLV}))
417 	];
418 };
419 
420 ZmDumpsterTaskListView.prototype._getType =
421 function() {
422 	return ZmItem.TASK;
423 };
424 
425 ZmDumpsterTaskListView.prototype._getCellContents =
426 function(htmlArr, idx, item, field, colIdx, params) {
427 	var content;
428 	if (field == ZmItem.F_SUBJECT) {
429 		var subj = item.name;
430 		content = AjxStringUtil.htmlEncode(subj);
431 	}
432 	else if (field == ZmItem.F_STATUS) {
433 		var status = item.status;
434 		content = ZmCalItem.getLabelForStatus(item.status);
435 	}
436 	else if (field == ZmItem.F_DATE) {
437 		content = item.endDate != null
438 			? AjxDateUtil.simpleComputeDateStr(item.endDate)
439 			: " ";
440 	}
441 	if (content) {
442 		htmlArr[idx++] = content;
443 	} else {
444 		idx = ZmListView.prototype._getCellContents.apply(this, arguments);
445 	}
446 	return idx;
447 };
448 
449 ZmDumpsterTaskListView.prototype._getToolTip =
450 function(params) {
451 	return params.item.fragment;
452 };
453 
454 
455 /**
456  * Listview showing deleted briefcase items
457  *
458  * @param parent
459  * @param controller
460  */
461 ZmDumpsterBriefcaseListView = function(parent, controller) {
462 	ZmDumpsterListView.call(this, parent, controller);
463 };
464 
465 ZmDumpsterBriefcaseListView.prototype = new ZmDumpsterListView;
466 ZmDumpsterBriefcaseListView.prototype.constructor = ZmDumpsterBriefcaseListView;
467 ZmDumpsterBriefcaseListView.prototype.toString =
468 function() {
469 	return "ZmDumpsterBriefcaseListView";
470 };
471 
472 ZmDumpsterBriefcaseListView.prototype._getHeaderList =
473 function() {
474 	return [
475 		(new DwtListHeaderItem({field:ZmItem.F_NAME, text:ZmMsg.name})),
476 		(new DwtListHeaderItem({field:ZmItem.F_FILE_TYPE, text:ZmMsg.type, width:ZmMsg.COLUMN_WIDTH_TYPE_DLV})),
477 		(new DwtListHeaderItem({field:ZmItem.F_SIZE, text:ZmMsg.size, width:ZmMsg.COLUMN_WIDTH_SIZE_DLV}))
478 	];
479 };
480 
481 ZmDumpsterBriefcaseListView.prototype._getType =
482 function() {
483 	return ZmItem.BRIEFCASE_ITEM;
484 };
485 
486 ZmDumpsterBriefcaseListView.prototype._getCellContents =
487 function(htmlArr, idx, item, field, colIdx, params) {
488 	var content;
489 	if (field == ZmItem.F_NAME) {
490 		var name = item.name;
491 		content = AjxStringUtil.htmlEncode(name);
492 	}
493 	else if (field == ZmItem.F_FILE_TYPE) {
494 		if (item.isFolder) {
495 		    content = ZmMsg.folder;
496 		} else {
497 		    var mimeInfo = item.contentType ? ZmMimeTable.getInfo(item.contentType) : null;
498 		    content = mimeInfo ? mimeInfo.desc : " ";
499 		}
500 	}
501 	else if (field == ZmItem.F_SIZE) {
502 		var size = item.size;
503 		content = AjxUtil.formatSize(size);
504 	}
505 	
506 	if (content) {
507 		htmlArr[idx++] = content;
508 	} else {
509 		idx = ZmListView.prototype._getCellContents.apply(this, arguments);
510 	}
511 	return idx;
512 };
513 
514 
515 /**
516  * Controller for the ZmDumpsterListView
517  *
518  * @param container		[DwtControl]	container this controller "controls"
519  */
520 ZmDumpsterListController = function(container) {
521 	ZmListController.call(this, container, appCtxt.getApp(ZmApp.MAIL));
522 };
523 
524 ZmDumpsterListController.prototype = new ZmListController;
525 ZmDumpsterListController.prototype.constructor = ZmDumpsterListController;
526 
527 ZmDumpsterListController.prototype.toString =
528 function() {
529 	return "ZmDumpsterListController";
530 };
531 
532 ZmDumpsterListController.prototype.show =
533 function(types, results) {
534 
535 	this._appName = ZmItem.APP[types[0]]; // All types should be in the same app
536 	var view = "dumpster" + this._appName;
537 	this.setCurrentViewId(view);
538 	for (var id in this._toolbar) {
539 		this._toolbar[id].setVisible(id == view);
540 	}
541 	for (var id in this._listView) {
542 		this._listView[id].setVisible(id == view);
543 	}
544 	if (results) {
545 		var searchResults = results.getResponse();
546 	
547 		// call base class
548 		ZmListController.prototype.show.call(this, searchResults, view);
549 
550 		this._setup(view);
551 		this._initializeSearchBar();
552 		this._container._updateTabGroup();
553 		this._container._inputField.getInputElement().focus(); //this is the only focus way I could get it to focus on the input element.
554 
555 		var list = searchResults.getResults(searchResults.type);
556 
557 		this.setList(list);
558 		this.setHasMore(searchResults.getAttribute("more"));
559 		this.getCurrentView().set(list);
560 	} else {
561 		this.cleanup();
562 	}
563 };
564 
565 ZmDumpsterListController.prototype.cleanup =
566 function() {
567 	var currentView = this.getCurrentView();
568 	if (currentView) {
569 		currentView.removeAll();
570 	}
571 };
572 
573 ZmDumpsterListController.prototype._createNewView =
574 function(view) {
575 	return ZmDumpsterListView.createView(view, this._container, this);
576 };
577 
578 ZmDumpsterListController.prototype._setViewContents	=
579 function(view) {
580 	this._listView[view].set(this._list);
581 };
582 
583 ZmDumpsterListController.prototype._searchListener =
584 function() {
585 	var dialog = this._container;
586 	var keywords = dialog.getSearchText();
587 	dialog.runSearchQuery(keywords);
588 };
589 
590 ZmDumpsterListController.prototype._initializeSearchBar =
591 function() {
592 	if (this._searchBarInitialized) {
593 		return;
594 	}
595 	this._searchBarInitialized = true;
596 	var dialog = this._container;
597 	dialog._initializeSearchBar(this._searchListener.bind(this));
598 };
599 
600 
601 ZmDumpsterListController.prototype._initializeToolBar =
602 function(view) {
603 	if (this._toolbar[view]) { return; }
604 
605 	var overrides = {};
606 	overrides[ZmOperation.MOVE] = {showImageInToolbar: false,
607 								   showTextInToolbar : true,
608 								   textKey: "recoverTo"};
609 	var tbParams = {
610 		parent:        this._container,
611 		className:     "ZmDumpsterDialog-toolbar",
612 		buttons:       [ZmOperation.MOVE],
613 		overrides:     overrides,
614 		posStyle:      Dwt.RELATIVE_STYLE,
615 		context:       view,
616 		controller:    this,
617 		parentElement: (this._container._htmlElId + "_toolbar")
618 	};
619 	var tb = this._toolbar[view] = new ZmButtonToolBar(tbParams);
620 	tb.addSelectionListener(ZmOperation.MOVE, new AjxListener(this, this._moveListener));
621 
622 };
623 
624 ZmDumpsterListController.prototype._doMove =
625 function(items, folder, attrs, isShiftKey) {
626 	if (!attrs) {
627 		attrs = {};
628 	}
629 	attrs.op = "recover";
630 	attrs.l = folder.id;
631 	                                                                                                                                              		
632 	ZmListController.prototype._doMove.call(this, items, folder, attrs, isShiftKey, true);
633 };
634 
635 ZmDumpsterListController.prototype._getMoreSearchParams =
636 function(params) {
637 	params.inDumpster = true;
638 };
639 
640 ZmDumpsterListController.prototype._getMoveParams =
641 function(dlg) {
642 	var params = ZmListController.prototype._getMoveParams.call(this, dlg);
643 	params.appName = this._appName;
644 	params.overviewId = dlg.getOverviewId(this._appName);
645 	params.treeIds = [ZmApp.ORGANIZER[this._appName]];
646 	params.acceptFolderMatch = true;
647 	params.showDrafts =	true;
648 	var omit = {};
649 	omit[ZmFolder.ID_SPAM] = true;
650 	//bug:60237 remote folders should be excluded from the recovery folder selection
651     var folderTree = appCtxt.getFolderTree();
652 	if (!folderTree) { return params; }
653 	var folders = folderTree.getByType(ZmApp.ORGANIZER[this._appName]);
654 	for (var i = 0; i < folders.length; i++) {
655 		var folder = folders[i];
656         if(folder.link && folder.isRemote()) {
657           omit[folder.id] = true;
658         }
659 	}
660 	params.omit = omit;
661 	return params;
662 };
663