1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2011, 2012, 2013, 2014, 2015, 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) 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 /**
 25  * @overview
 26  */
 27 
 28 // Creates a folder retention view for the folder dialog
 29 ZmFolderRetentionView = function(dialog, parent) {
 30     if (arguments.length == 0) return;
 31     ZmFolderDialogTabView.call(this, parent);
 32 
 33     this._dialog = dialog;
 34     this._initialized = false;
 35 	// Make sure mouse down clicks propagate to the select controls
 36 	this._propagateEvent[DwtEvent.ONMOUSEDOWN] = true;
 37 };
 38 
 39 ZmFolderRetentionView.prototype = new ZmFolderDialogTabView;
 40 ZmFolderRetentionView.prototype.constructor = ZmFolderRetentionView;
 41 
 42 // Data to populate and process the custom units and values
 43 ZmFolderRetentionView._CustomUnitsToDays = { year: 366, month:31, week:7, day:1};
 44 ZmFolderRetentionView._CustomUnits = [
 45     { id:"day",   label: ZmMsg.days.toLowerCase(),   days: ZmFolderRetentionView._CustomUnitsToDays.day},
 46     { id:"week",  label: ZmMsg.weeks.toLowerCase(),  days: ZmFolderRetentionView._CustomUnitsToDays.week},
 47     { id:"month", label: ZmMsg.months.toLowerCase(), days: ZmFolderRetentionView._CustomUnitsToDays.month},
 48     { id:"year",  label: ZmMsg.years.toLowerCase(),  days: ZmFolderRetentionView._CustomUnitsToDays.year}];
 49 
 50 
 51 
 52 ZmFolderRetentionView.prototype.toString =
 53 function() {
 54 	return "ZmFolderRetentionView";
 55 };
 56 
 57 ZmFolderRetentionView.prototype.getTitle =
 58 function() {
 59     return ZmMsg.folderTabRetention;
 60 }
 61 
 62 ZmFolderRetentionView.prototype.showMe =
 63 function() {
 64 	DwtTabViewPage.prototype.showMe.call(this);
 65     this._dialog.setButtonVisible(ZmFolderPropsDialog.ADD_SHARE_BUTTON, false);
 66 
 67 	this.setSize(Dwt.DEFAULT, "200");
 68 
 69 };
 70 
 71 ZmFolderRetentionView.prototype._handleFolderChange =
 72 function() {
 73     // Read the policies from the server, and add 'Custom'
 74     if (this._initialized) {
 75         this._dataBindComponents(this._organizer, ZmOrganizer.RETENTION_KEEP);
 76         this._dataBindComponents(this._organizer, ZmOrganizer.RETENTION_PURGE);
 77     } else {
 78         var systemPolicies = new ZmSystemRetentionPolicy();
 79         systemPolicies.getPolicies(this._processSystemPolicies.bind(this));
 80         this._setBusy(true);
 81     }
 82 }
 83 
 84 ZmFolderRetentionView.prototype._processSystemPolicies =
 85 function(systemKeepPolicies, systemPurgePolicies) {
 86 
 87     this._populatePolicySelect(ZmOrganizer.RETENTION_KEEP,  systemKeepPolicies);
 88     this._populatePolicySelect(ZmOrganizer.RETENTION_PURGE, systemPurgePolicies);
 89 
 90     this._dataBindComponents(this._organizer, ZmOrganizer.RETENTION_KEEP);
 91     this._dataBindComponents(this._organizer, ZmOrganizer.RETENTION_PURGE);
 92 
 93     this._initialized = true;
 94     this._setBusy(false);
 95 }
 96 
 97 
 98 ZmFolderRetentionView.prototype._dataBindComponents =
 99 function(organizer, policyElement) {
100     var components = this._components[policyElement];
101     components.policyEnable.checked = false;
102 
103     var policy = organizer.getRetentionPolicy(policyElement);
104     if (policy) {
105         // The organizer has a retention policy
106         components.policyEnable.checked = true;
107 
108         if (policy.type == "user") {
109             // Custom policy defined.
110             components.policySelect.selectedIndex = components.policySelect.options.length-1;
111             // parseInt will discard the unit
112             var lifetime = parseInt(policy.lifetime);
113             // In case someone used SOAP to specify a custom policy, convert it
114             // to days (which is the smallest unit we can handle via the UI).
115             var conversionFactor = 1;
116             // Intervals taken from DateUtil.java
117             var interval = policy.lifetime.slice(policy.lifetime.length-1);
118             switch (interval) {
119                 case  "d": conversionFactor =  1; break;
120                 case  "h": conversionFactor = 24; break;
121                 case  "m": conversionFactor = AjxDateUtil.MINUTES_PER_DAY; break;
122                 case  "s": conversionFactor = AjxDateUtil.SECONDS_PER_DAY; break;
123                 case "ms": conversionFactor = AjxDateUtil.MSEC_PER_DAY;    break;
124                 default  : conversionFactor = AjxDateUtil.SECONDS_PER_DAY; break;
125             }
126             var days = Math.floor((lifetime-1)/conversionFactor) + 1;
127 
128             // Convert lifetime to the best fit for unit and amount.  Start with year,
129             // proceed to smaller units.  If the amount in days is evenly divisible by the
130             // days for a unit, use that unit
131             for (var i = ZmFolderRetentionView._CustomUnits.length-1; i >= 0; i--) {
132                 if ((days >= ZmFolderRetentionView._CustomUnits[i].days) &&
133                     ((days % ZmFolderRetentionView._CustomUnits[i].days) == 0)) {
134                     components.customUnit.selectedIndex = i;
135                     components.customValue.value = days/ZmFolderRetentionView._CustomUnits[i].days;
136                     break;
137                 }
138             }
139         } else {
140             // System policy, find the match in the policy selection pull down
141             for (var i = 0; i < components.policySelect.options.length; i++) {
142                 if (components.policySelect.options[i].value == policy.id) {
143                     components.policySelect.selectedIndex = i;
144                     break;
145                 }
146             }
147             // Reset custom fields to their defaults
148             components.customUnit.selectedIndex   = 0;
149             components.customValue.value          = "";
150         }
151     }
152     if (!components.policyEnable.checked) {
153         // No policy of this type (keep/purge) reset the selects and input fields
154         components.policySelect.selectedIndex = 0;
155         // Default to the largest unit to reduce the chance of an inadvertent tiny deletion period
156         components.customUnit.selectedIndex   =  components.customUnit.options.length-1;
157         components.customValue.value          = "";
158     }
159     this._handleSelectionChange(policyElement);
160     this._handleEnableClick(policyElement);
161 }
162 
163 
164 /**  doSave will be invoked for each tab view.
165  *
166  * @param	{BatchCommand}	batchCommand	Accumulates updates from all tabs
167  * @param	{Object}	    saveState		Accumulates error messages and indication of any update
168  */
169 ZmFolderRetentionView.prototype.doSave =
170 function(batchCommand, saveState) {
171 	if (!this._handleErrorCallback) {
172 		this._handleErrorCallback = new AjxCallback(this, this._handleError);
173 	}
174 
175     var organizer = this._organizer;
176 
177     var initialErrorCount = saveState.errorMessage.length;
178     var newRetentionPolicy = { };
179     // Create policy objects from the UI fields, attach to the newRetentionPolicy variable
180     this._createPolicy(newRetentionPolicy, ZmOrganizer.RETENTION_KEEP,  saveState);
181     this._createPolicy(newRetentionPolicy, ZmOrganizer.RETENTION_PURGE, saveState);
182 
183     if (initialErrorCount == saveState.errorMessage.length) {
184         var keepPolicy  = organizer.getRetentionPolicy(ZmOrganizer.RETENTION_KEEP);
185         var purgePolicy = organizer.getRetentionPolicy(ZmOrganizer.RETENTION_PURGE);
186         if (organizer.policiesDiffer(keepPolicy,  newRetentionPolicy.keep) ||
187             organizer.policiesDiffer(purgePolicy, newRetentionPolicy.purge)) {
188             // Retention policy has changed
189             batchCommand.add(new AjxCallback(organizer, organizer.setRetentionPolicy,
190                              [newRetentionPolicy, null, this._handleErrorCallback]));
191             saveState.commandCount++;
192         }
193     }
194 
195 };
196 
197 
198 // Create a retention policy object from UI components for processing by the
199 // organizer.setRetentionPolicy
200 ZmFolderRetentionView.prototype._createPolicy =
201 function(retentionPolicy, policyElement, saveState) {
202     var components = this._components[policyElement];
203 
204     var policy;
205     if (components.policyEnable.checked) {
206         // A keep or Purge retention policy is defined
207         var policySelection = components.policySelect.options[components.policySelect.selectedIndex].value;
208         var policyType;
209         if (policySelection == "custom") {
210             policyType = "user";
211             var unit   = components.customUnit.options[components.customUnit.selectedIndex].value;
212             // Parse the custom value field to get the number of units
213             var invalidCustomValue = false;
214             var amountText = AjxStringUtil.trim(components.customValue.value);
215             if (!amountText) {
216                 invalidCustomValue = true;
217             } else {
218                 var amount = 0;
219                 var nonNumericIndex = amountText.search(/\D/);
220                 if (nonNumericIndex == -1) {
221                     amount = parseInt(amountText);
222                 }
223 
224                 if (amount <= 0) {
225                     invalidCustomValue = true;
226                 } else {
227                     var daysPerUnit = ZmFolderRetentionView._CustomUnitsToDays[unit];
228                     var lifetime = (amount * daysPerUnit).toString() + "d";
229                     policy = {type:"user", lifetime:lifetime};
230                 }
231             }
232             if (invalidCustomValue) {
233                 var  errorMessage = (policyElement == ZmOrganizer.RETENTION_KEEP) ?
234                                      ZmMsg.retentionKeepLifetimeAmount : ZmMsg.retentionPurgeLifetimeAmount;
235                 saveState.errorMessage.push(errorMessage);
236             }
237         } else {
238             policy = {type:"system", id:policySelection};
239         }
240     }
241     retentionPolicy[policyElement] = policy;
242 }
243 
244 
245 ZmFolderRetentionView.prototype._populatePolicySelect =
246 function(policyElement, systemPolicies) {
247     var components = this._components[policyElement];
248     components.policies = systemPolicies ? systemPolicies : [];
249 	
250 	var sorted = {};
251 	for (var i=0; i< components.policies.length; i++) {
252 		sorted[components.policies[i].name] =  components.policies[i].id;
253 	}
254 	
255 	var sortedKeys = AjxUtil.getHashKeys(sorted);
256 	sortedKeys.push(ZmMsg.custom);  //append custom to the end
257 	sorted[ZmMsg.custom] = "custom";
258     components.policySelect.options.length = 0;
259     for (var i = 0; i < sortedKeys.length; i++) {
260         components.policySelect.options[i] = new Option(sortedKeys[i], sorted[sortedKeys[i]]);
261     }
262 }
263 
264 
265 ZmFolderRetentionView.prototype._handleEnableClick =
266 function(policyElement) {
267     var components = this._components[policyElement];
268     var disabled = !components.policyEnable.checked;
269     components.policySelect.disabled = disabled;
270     components.customValue.disabled  = disabled;
271     components.customUnit.disabled   = disabled;
272 }
273 
274 ZmFolderRetentionView.prototype._handleSelectionChange = function(policyElement) {
275 
276     var components = this._components[policyElement],
277         policySelect   = components.policySelect,
278 	    selectedOption = policySelect.options[policySelect.selectedIndex],
279         policySelection = selectedOption.value,
280         visible = (policySelection == "custom");
281 
282     // Show hide the custom unit and values fields based on whether the policy
283     // selected is a system defined policy, or custom
284     components.customValue.style.visibility = visible ? "visible" : "hidden";
285     components.customUnit.style.visibility  = visible ? "visible" : "hidden";
286 
287 	// accessibility
288 	policySelect.setAttribute('aria-label', AjxMessageFormat.format(ZmMsg.policyTypeLabel, selectedOption.innerHTML));
289 };
290 
291 ZmFolderRetentionView.prototype._createView =
292 function() {
293 	// Create html elements
294     this._id = Dwt.getNextId();
295     var params = {
296         id: this._id
297     }
298 
299     var container = this.getHtmlElement();
300     container.innerHTML = AjxTemplate.expand("share.Dialogs#ZmFolderRetentionView", params);
301 
302     this._components = {};
303     this._setupComponents(ZmOrganizer.RETENTION_KEEP,  this._components);
304     this._setupComponents(ZmOrganizer.RETENTION_PURGE, this._components);
305 
306     this._createBusyOverlay(container);
307     this._contentEl = container;
308 };
309 
310 ZmFolderRetentionView.prototype._setupComponents =
311 function(policyElement, allComponents) {
312 
313     var components = {};
314     allComponents[policyElement] = components;
315 
316     components.policyEnable = document.getElementById(this._id + "_" + policyElement + "Checkbox");
317     components.policySelect = document.getElementById(this._id + "_" + policyElement);
318     components.customValue  = document.getElementById(this._id + "_" + policyElement + "Value");
319     components.customUnit   = document.getElementById(this._id + "_" + policyElement + "Unit");
320 
321     for (var i = 0; i < ZmFolderRetentionView._CustomUnits.length; i++) {
322         var unit = ZmFolderRetentionView._CustomUnits[i];
323         components.customUnit.options[i] = new Option(unit.label, unit.id);
324     }
325 
326 	this._tabGroup.addMember(components.policyEnable);
327 	this._tabGroup.addMember(components.policySelect);
328 	this._tabGroup.addMember(components.customValue);
329 	this._tabGroup.addMember(components.customUnit);
330 
331     Dwt.setHandler(components.policyEnable, "onclick", this._handleEnableClick.bind(this, policyElement));
332     Dwt.setHandler(components.policySelect, "onclick", this._handleSelectionChange.bind(this, policyElement));
333 }
334