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