1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2009, 2010, 2011, 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) 2009, 2010, 2011, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 /** 25 * @overview 26 * This file defines classes for the storage/retrieval of generic user data. 27 */ 28 29 /** 30 * Stores generic data to the server via <code><Set[Mailbox|Custom]MetadataRequest></code> and 31 * <code><Get[Mailbox|Custom]MetadataRequest></code>. 32 * @class 33 * This class provides a general way to store per-user data using arbitrary 34 * key/value pairs. NOTE: the server does not support modifying data so if there 35 * are any changes, *all* the data must be re-set per section. Data can be 36 * written on the mailbox or on an individual mailbox item. If the constructor 37 * receives an itemId, then data will be retrieved/written via [Get|Set]CustomMetadataRequest. 38 * <br/> 39 * <br/> 40 * The section data is mapped into {@link ZmSettings} (based on the key name) to allow 41 * for easy access. When creating/setting a *new* key/value, naming conventions 42 * should be followed as defined by prefs in {@link ZmSettings}. For example, if adding 43 * a new key called "foo", the name for the key should be "zimbraPrefFoo" and 44 * should be added to the list of settings in {@link ZmSettings} with type set to 45 * ZmSetting.T_METADATA 46 * 47 * @param {ZmAccount} account Optional. The account this meta data belongs to 48 * @param {String} itemId Optional. If specified, (custom) meta data will be saved on the item 49 * 50 * @author Parag Shah 51 */ 52 ZmMetaData = function(account, itemId) { 53 54 this._sections = {}; 55 this._account = account; 56 this._itemId = itemId; 57 }; 58 59 ZmMetaData.prototype.constructor = ZmMetaData; 60 61 62 // Consts 63 64 ZmMetaData.NAMESPACE = "zwc"; 65 66 67 // Public Methods 68 69 /** 70 * Returns a string representation of the object. 71 * 72 * @return {String} a string representation of the object 73 */ 74 ZmMetaData.prototype.toString = 75 function() { 76 return "ZmMetaData"; 77 }; 78 79 /** 80 * Saves the given section and corresponding key/value pair to the server. 81 * 82 * @param {String} section the name of the section to save 83 * @param {Object} data the list of key/value pairs 84 * @param {ZmBatchCommand} batchCommand if part of a batch command 85 * @param {AjxCallback} callback the callback to trigger on successful save 86 * @param {AjxCallback} errorCallback the error callback to trigger on error 87 * @param {Boolean} sensitive if <code>true</code>, attempt to use secure conn to protect data 88 */ 89 ZmMetaData.prototype.set = 90 function(section, data, batchCommand, callback, errorCallback, sensitive) { 91 var soapDoc; 92 if (this._itemId) { 93 soapDoc = AjxSoapDoc.create("SetCustomMetadataRequest", "urn:zimbraMail"); 94 soapDoc.getMethod().setAttribute("id", this._itemId); 95 } else { 96 soapDoc = AjxSoapDoc.create("SetMailboxMetadataRequest", "urn:zimbraMail"); 97 } 98 var metaNode = soapDoc.set("meta"); 99 metaNode.setAttribute("section", [ZmMetaData.NAMESPACE, section].join(":")); 100 101 for (var i in data) { 102 var d = data[i]; 103 104 // serialize if we're dealing with an object 105 if (AjxUtil.isObject(d)) { 106 d = ZmSetting.serialize(d, ZmSetting.D_HASH); 107 } 108 var a = soapDoc.set("a", d, metaNode); 109 a.setAttribute("n", i); 110 } 111 112 if (batchCommand) { 113 batchCommand.addNewRequestParams(soapDoc, callback, errorCallback); 114 } 115 else { 116 var params = { 117 soapDoc: soapDoc, 118 asyncMode: true, 119 callback: callback, 120 errorCallback: errorCallback, 121 accountName: (this._account ? this._account.name : null), 122 sensitive: sensitive 123 }; 124 125 appCtxt.getAppController().sendRequest(params); 126 } 127 }; 128 129 /** 130 * Fetches the given section name from the server unless its already been 131 * fetched (and therefore cached) 132 * 133 * @param {String} section section of meta data to fetch 134 * @param {ZmBatchCommand} batchCommand if part of a separate batch command 135 * @param {AjxCallback} callback the callback to trigger once meta data is fetched 136 * @param {AjxCallback} errorCallback the error callback to trigger on error 137 */ 138 ZmMetaData.prototype.get = 139 function(section, batchCommand, callback, errorCallback) { 140 var command = batchCommand || (new ZmBatchCommand()); 141 var sectionName = [ZmMetaData.NAMESPACE, section].join(":"); 142 143 var cachedSection = this._sections[sectionName]; 144 145 // if not yet cached, go fetch it 146 if (!cachedSection) { 147 var soapDoc; 148 if (this._itemId) { 149 soapDoc = AjxSoapDoc.create("GetCustomMetadataRequest", "urn:zimbraMail"); 150 soapDoc.getMethod().setAttribute("id", this._itemId); 151 } else { 152 soapDoc = AjxSoapDoc.create("GetMailboxMetadataRequest", "urn:zimbraMail"); 153 } 154 var metaNode = soapDoc.set("meta"); 155 metaNode.setAttribute("section", sectionName); 156 157 command.addNewRequestParams(soapDoc); 158 159 if (!batchCommand) { 160 command.run(callback, errorCallback); 161 } 162 } 163 else { 164 if (callback) { 165 callback.run(cachedSection); 166 } else { 167 return cachedSection; 168 } 169 } 170 }; 171 172 /** 173 * Loads meta data from the server 174 * 175 * @param {Array} sections the sections to load 176 * @param {AjxCallback} callback the callback 177 * @param {ZmBatchCommand} batchCommand if part of batch command 178 */ 179 ZmMetaData.prototype.load = 180 function(sections, callback, batchCommand) { 181 if (!sections) { return; } 182 if (!(sections instanceof Array)) { sections = [sections]; } 183 184 var command = batchCommand || (new ZmBatchCommand()); 185 for (var i = 0; i < sections.length; i++) { 186 if (sections[i] == ZmSetting.M_OFFLINE && !appCtxt.isOffline) { continue; } 187 this.get(sections[i], command); 188 } 189 190 if (!batchCommand) { 191 if (command.size() > 0) { 192 var respCallback = new AjxCallback(this, this._handleLoad, [callback]); 193 var offlineCallback = this._handleOfflineLoad.bind(this, respCallback); 194 command.run(respCallback, null, offlineCallback); 195 } 196 } else { 197 if (callback) { 198 callback.run(this._sections); 199 } 200 } 201 }; 202 203 /** 204 * @private 205 */ 206 ZmMetaData.prototype._handleLoad = 207 function(callback, result) { 208 this._sections = {}; 209 210 var br = result.getResponse().BatchResponse; 211 if (br) { 212 var metaDataResp = (this._itemId != null) ? br.GetCustomMetadataResponse : br.GetMailboxMetadataResponse; 213 if (metaDataResp && metaDataResp.length) { 214 if (ZmOffline.isOnlineMode()) { 215 localStorage.setItem("MetadataResponse", JSON.stringify(br)); 216 } 217 for (var i = 0; i < metaDataResp.length; i++) { 218 var data = metaDataResp[i].meta[0]; 219 this._sections[data.section] = data._attrs; 220 } 221 } 222 } 223 224 if (callback) { 225 callback.run(this._sections); 226 } 227 }; 228 229 /** 230 * @private 231 */ 232 ZmMetaData.prototype._handleOfflineLoad = 233 function(callback) { 234 var result = localStorage.getItem("MetadataResponse"); 235 if (result) { 236 var csfeResult = new ZmCsfeResult({BatchResponse : JSON.parse(result)}); 237 callback.run(csfeResult); 238 } 239 }; 240 241 /** 242 * Modifies the given section with new key/value pairs 243 * 244 * @param {Array} section the section to modify 245 * @param {Object} data the list of key/value pairs 246 * @param {ZmBatchCommand} batchCommand Optional. the batch command the request should be a part of 247 * @param {AjxCallback} callback the callback called on successful modify 248 * @param {AjxCallback} errorCallback the error callback to trigger on error 249 */ 250 ZmMetaData.prototype.modify = 251 function(section, data, batchCommand, callback, errorCallback) { 252 var soapDoc = AjxSoapDoc.create("ModifyMailboxMetadataRequest", "urn:zimbraMail"); 253 var metaNode = soapDoc.set("meta"); 254 metaNode.setAttribute("section", [ZmMetaData.NAMESPACE, section].join(":")); 255 256 for (var i in data) { 257 var a = soapDoc.set("a", data[i], metaNode); 258 a.setAttribute("n", i); 259 } 260 261 if (batchCommand) { 262 batchCommand.addNewRequestParams(soapDoc, callback, errorCallback); 263 } 264 else { 265 var params = { 266 soapDoc: soapDoc, 267 asyncMode: true, 268 callback: callback, 269 errorCallback: errorCallback, 270 accountName: (this._account ? this._account.name : null) 271 }; 272 273 appCtxt.getAppController().sendRequest(params); 274 } 275 }; 276 277 /** 278 * Saves all data within the given section out to the server. If section is not 279 * provided, all sections are saved. 280 * 281 * @param {Array} sections the sections to save 282 * @param {AjxCallback} callback the callback called on successful save 283 * @param {ZmBatchCommand} batchCommand the batch command the request should be a part of 284 */ 285 ZmMetaData.prototype.save = 286 function(sections, callback, batchCommand) { 287 if (!sections) { return; } 288 if (!(sections instanceof Array)) { sections = [sections]; } 289 290 var acct = this._account ? this._account.name : null; 291 var command = batchCommand || (new ZmBatchCommand(null, acct)); 292 293 for (var i = 0; i < sections.length; i++) { 294 var s = sections[i]; 295 var sectionName = [ZmMetaData.NAMESPACE, s].join(":"); 296 var sectionData = this._sections[sectionName]; 297 if (sectionData) { 298 this.set(s, sectionData, command); 299 } 300 } 301 302 if (!batchCommand) { 303 if (command.size() > 0) { 304 command.run(callback); 305 } 306 } else { 307 if (callback) { 308 callback.run(); 309 } 310 } 311 }; 312 313 /** 314 * Updates the local section cache with the given key/value pair. 315 * 316 * @param {String} section the section to update 317 * @param {String} key the key to update 318 * @param {String} value the new value 319 */ 320 ZmMetaData.prototype.update = 321 function(section, key, value) { 322 var sectionName = [ZmMetaData.NAMESPACE, section].join(":"); 323 var sectionObj = this._sections[sectionName]; 324 if (!sectionObj) { 325 sectionObj = this._sections[sectionName] = {}; 326 } 327 sectionObj[key] = value; 328 }; 329