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