1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2005, 2006, 2007, 2008, 2009, 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) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 /**
 25  * @overview
 26  * This file contains the Zimlet context class.
 27  */
 28 
 29 /**
 30  * Creates the Zimlet context.
 31  * @class
 32  * This class represents the Zimlet context.
 33  * 
 34  * @param	{String}	id		the id
 35  * @param	{ZmZimletBase}	zimlet	the Zimlet
 36  * 
 37  */
 38 ZmZimletContext = function(id, zimlet) {
 39 
 40 	// sanitize JSON here
 41 	this.json = ZmZimletContext.sanitize(zimlet, "zimlet", ZmZimletContext.RE_ARRAY_ELEMENTS);
 42 
 43 	this.id = id;
 44 	this.icon = "ZimbraIcon";
 45 	this.ctxt = zimlet.zimletContext;
 46 	this.config = zimlet.zimletConfig;
 47 	zimlet = zimlet.zimlet[0];
 48 	/**
 49 	 * The zimlet name.
 50 	 * @type String
 51 	 */
 52 	this.name = zimlet.name;
 53 	this._url = this.ctxt[0].baseUrl;
 54 	this.priority = this.ctxt[0].priority;
 55 	/**
 56 	 * The zimlet description.
 57 	 * @type String
 58 	 */
 59 	this.description = zimlet.description;
 60 	/**
 61 	 * The zimlet version.
 62 	 * @type String
 63 	 */
 64 	this.version = zimlet.version;
 65 	this.label = zimlet.label;
 66 	this.includes = this.json.zimlet.include || [];
 67 	this.includes.push([appContextPath, "/messages/", this.name, ".js?v=", cacheKillerVersion].join(""));
 68 	this.includeCSS = this.json.zimlet.includeCSS;
 69 
 70 	if (zimlet.serverExtension && zimlet.serverExtension[0].hasKeyword) {
 71 		this.keyword = zimlet.serverExtension[0].hasKeyword;
 72 	}
 73 
 74 	DBG.println(AjxDebug.DBG2, "Zimlets - context: " + this.name);
 75 
 76 	this.targets = {};
 77 	var targets = (zimlet.target || "main").split(" ");
 78 	for (var i = 0; i < targets.length; i++) {
 79 		this.targets[targets[i]] = true;
 80 	}
 81 
 82 	this._contentActionMenu = null;
 83 	if (zimlet.contentObject) {
 84 		this.contentObject = zimlet.contentObject[0];
 85 		if (this.contentObject.type) {
 86 			this.type = this.contentObject.type;
 87 		}
 88 		if (this.contentObject.contextMenu) {
 89 			if (this.contentObject.contextMenu instanceof Array) {
 90 				this.contentObject.contextMenu = this.contentObject.contextMenu[0];
 91 			}
 92 			this._contentActionMenu = new AjxCallback(this, this._makeMenu,[this.contentObject.contextMenu.menuItem]);
 93 		}
 94 	}
 95 
 96 	this._panelActionMenu = null;
 97 	if (zimlet.zimletPanelItem && !appCtxt.isChildWindow) {
 98 		this.zimletPanelItem = zimlet.zimletPanelItem[0];
 99 		if (this.zimletPanelItem.label) {
100 			this.zimletPanelItem.label = this.process(this.zimletPanelItem.label);
101 		}
102 		if (this.zimletPanelItem.toolTipText && this.zimletPanelItem.toolTipText[0]) {
103 			this.zimletPanelItem.toolTipText = this.process(this.zimletPanelItem.toolTipText[0]._content);
104 		}
105 		if (this.zimletPanelItem.icon) {
106 			this.icon = this.zimletPanelItem.icon;
107 		}
108 		if (this.zimletPanelItem.contextMenu) {
109 			if (this.zimletPanelItem.contextMenu instanceof Array) {
110 				this.zimletPanelItem.contextMenu = this.zimletPanelItem.contextMenu[0];
111 			}
112 			this._panelActionMenu = new AjxCallback(this, this._makeMenu, [this.zimletPanelItem.contextMenu.menuItem]);
113 		}
114 		if (this.zimletPanelItem.onClick instanceof Array) {
115 			this.zimletPanelItem.onClick = this.zimletPanelItem.onClick[0];
116 		}
117 		if (this.zimletPanelItem.onDoubleClick instanceof Array) {
118 			this.zimletPanelItem.onDoubleClick = this.zimletPanelItem.onDoubleClick[0];
119 		}
120 	}
121 
122 	if (zimlet.handlerObject) {
123 		this.handlerObject = zimlet.handlerObject[0]._content;
124 	}
125 
126 	var portlet = zimlet.portlet && zimlet.portlet[0];
127 	if (portlet) {
128 		portlet = ZmZimletContext.sanitize(portlet);
129 		portlet.portletProperties = (portlet.portletProperties && portlet.portletProperties.property) || {};
130 		this.portlet = portlet;
131 	}
132 
133 	this.userProperties = zimlet.userProperties ? zimlet.userProperties[0] : [];
134 	this._propsById = {};
135 	if (zimlet.userProperties) {
136 		this._translateUserProp();
137 	}
138 
139 	if (this.config) {
140 		if (this.config instanceof Array ||
141 			(appCtxt.isChildWindow && this.config.length && this.config[0])) {
142 
143 			this.config = this.config[0];
144 		}
145 		this._translateConfig();
146 	}
147 
148 	this._handleMenuItemSelected = new AjxListener(this, this._handleMenuItemSelected);
149 };
150 
151 ZmZimletContext.prototype.constructor = ZmZimletContext;
152 
153 
154 //
155 // Consts
156 //
157 ZmZimletContext.RE_ARRAY_ELEMENTS = /^(dragSource|include|includeCSS|menuItem|param|property|resource|portlet)$/;
158 ZmZimletContext.APP = {
159 	contextPath: appContextPath,
160 	currentSkin: appCurrentSkin
161 };
162 
163 // NOTE: I have no idea why these regexes start with (^|[^\\]). But
164 //       since they have always been public, I can't change them now.
165 ZmZimletContext.RE_SCAN_APP = /(^|[^\\])\$\{app\.([\$a-zA-Z0-9_]+)\}/g;
166 ZmZimletContext.RE_SCAN_OBJ = /(^|[^\\])\$\{(?:obj|src)\.([\$a-zA-Z0-9_]+)\}/g;
167 ZmZimletContext.RE_SCAN_PROP = /(^|[^\\])\$\{prop\.([\$a-zA-Z0-9_]+)\}/g;
168 ZmZimletContext.RE_SCAN_MSG = /(^|[^\\])\$\{msg\.([\$a-zA-Z0-9_]+)\}/g;
169 
170 ZmZimletContext.__RE_SCAN_SETTING = /\$\{setting\.([\$a-zA-Z0-9_]+)\}/g;
171 
172 /**
173  * This function creates a 'sane' JSON object, given one returned by the
174  * Zimbra server.
175  *<p>
176  * It will basically remove unnecessary arrays and create String objects for
177  * those tags that have text data, so that we don't need to dereference lots of
178  * arrays and use _content. It does the job that the server should do.  *grin*
179  * </p>
180  * <b>WARNING</b>: usage of an attribute named "length" may give sporadic
181  * results, since we convert tags that have text content to Strings.
182  *
183  * @param obj -- array or object, whatever was given by server
184  * @param tag -- the tag of this object, if it's an array
185  * @param wantarray_re -- RegExp that matches tags that must remain an array
186  *
187  * @return -- sanitized object
188  * 
189  * @private
190  */
191 ZmZimletContext.sanitize =
192 function(obj, tag, wantarray_re) {
193 	function doit(obj, tag) {
194 		var cool_json, val, i;
195 		if (obj instanceof DwtControl) { //Don't recurse into DwtControls, causes too much recursion
196 			return obj;
197 		}
198 		else if (obj instanceof Array || AjxUtil.isArray1(obj)) {
199 			if (obj.length == 1 && !(wantarray_re && wantarray_re.test(tag))) {
200 				cool_json = doit(obj[0], tag);
201 			} else {
202 				cool_json = [];
203 				for (i = 0; i < obj.length; ++i) {
204 					cool_json[i] = doit(obj[i], tag);
205 				}
206 			}
207 		}
208 		else if (obj && typeof obj == "object") {
209 			if (obj._content) {
210 				cool_json = new String(obj._content);
211 			} else {
212 				cool_json = {};
213 			}
214 			for (i in obj) {
215 				cool_json[i] = doit(obj[i], i);
216 			}
217 		} else {
218 			cool_json = obj;
219 		}
220 		return cool_json;
221 	}
222 	return doit(obj, tag);
223 };
224 
225 /**
226  * Returns a string representation of the object.
227  * 
228  * @return		{String}		a string representation of the object
229  */
230 ZmZimletContext.prototype.toString =
231 function() {
232 	return "ZmZimletContext - " + this.name;
233 };
234 
235 /**
236  * <strong>Note:</strong>
237  * This method is called by ZmZimletMgr#_finished_loadIncludes.
238  * 
239  * @private
240  */
241 ZmZimletContext.prototype._finished_loadIncludes =
242 function() {
243     // localize messages
244     this.label = this.label && this.processMessage(this.label);
245     this.description = this.description && this.processMessage(this.description);
246 
247 	var CTOR = this.handlerObject ? window[this.handlerObject] : ZmZimletBase;
248 	if (!CTOR) {
249 		DBG.println("zimlet handler not defined ("+this.handlerObject+")");
250 		return;
251 	}
252 	this.handlerObject = new CTOR();
253 	if (!this.handlerObject._init) {
254 		var msg = [
255 			"ERROR - Zimlet handler (",
256 			this.name,
257 			") not defined. ",
258 			"Make sure the Zimlet name and handlerObject defined in ",
259 			this.name,
260 			".xml are different."
261 		].join("");
262 		DBG.println(AjxDebug.DBG1, msg);
263 	}
264 	this.handlerObject._init(this, DwtShell.getShell(window));
265 	if (this.contentObject) {
266 		appCtxt.getZimletMgr().registerContentZimlet(this.handlerObject, this.type, this.priority);
267 	}
268 	this.handlerObject.init();
269 	this.handlerObject._zimletContext = this;
270 	// If it has an _id then we need to make sure the treeItem is up-to-date now
271 	// that the i18n files have loaded.
272 	if (this._id) {
273 		var acct = appCtxt.isOffline ? appCtxt.accountList.mainAccount : null;
274 		var tree = appCtxt.getZimletTree(acct);
275 		if (tree) {
276 			var zimletItem = tree.getById(this._id);
277 			zimletItem.resetNames();
278 		}
279 	}
280 
281 	// initialize portlets
282 	if (appCtxt.get(ZmSetting.PORTAL_ENABLED) && !appCtxt.isChildWindow) {
283 		var params = {
284 			name: "Portal",
285 			callback: new AjxCallback(this, this._finished_loadIncludes2)
286 		};
287 		DBG.println("------------------- REQUIRING Portal (ZmZimletContext)");
288 		AjxPackage.require(params);
289 	}
290 
291 	DBG.println(AjxDebug.DBG2, "Zimlets - init() complete: " + this.name);
292 };
293 
294 /**
295  * @private
296  */
297 ZmZimletContext.prototype._finished_loadIncludes2 =
298 function() {
299 	appCtxt.getApp(ZmApp.PORTAL).getPortletMgr().zimletLoaded(this);
300 };
301 
302 /**
303  * Gets the organizer.
304  * 
305  * @return	{ZmOrganizer}	the organizer
306  */
307 ZmZimletContext.prototype.getOrganizer =
308 function() {
309 	// this._organizer is a ZmZimlet and is set in ZmZimlet.createFromJs
310 	return this._organizer;
311 };
312 
313 /**
314  * Gets the URL.
315  * 
316  * @return	{String}	the URL
317  */
318 ZmZimletContext.prototype.getUrl =
319 function() {
320 	return this._url;
321 };
322 
323 /**
324  * Gets the value.
325  * 
326  * @param	{String}	key		the key
327  * @return	{Object}	the value
328  */
329 ZmZimletContext.prototype.getVal =
330 function(key) {
331 	var ret = this.json.zimlet;
332 	var keyParts = key.split('.');
333 	for (var i = 0; i < keyParts.length; i++) {
334 		ret = ret[keyParts[i]];
335 	}
336 	return ret;
337 };
338 
339 /**
340  * Calls the handler.
341  * 
342  * @param	{String}	funcname		the function
343  * @param	{Hash}		args			the arguments
344  * @return	{Object}	the results or <code>null</code> for none
345  * 
346  * @private
347  */
348 ZmZimletContext.prototype.callHandler =
349 function(funcname, args) {
350 	if (this.handlerObject) {
351 		var f = this.handlerObject[funcname];
352 		if (typeof f == "function") {
353 			if (typeof args == "undefined") {
354 				args = [];
355 			}
356 			else if (!(args instanceof Array)) {
357 				args = [args];
358 			}
359 			return f.apply(this.handlerObject, args);
360 		}
361 	}
362 	return null;
363 };
364 
365 /**
366  * @private
367  */
368 ZmZimletContext.prototype._translateUserProp =
369 function() {
370 	var a = this.userProperties = this.userProperties.property;
371 	for (var i = 0; i < a.length; ++i) {
372 		this._propsById[a[i].name] = a[i];
373 	}
374 };
375 
376 /**
377  * Sets the property.
378  * 
379  * @param	{String}	name		the name
380  * @param	{Object}	val			the value
381  * 
382  */
383 ZmZimletContext.prototype.setPropValue =
384 function(name, val) {
385 	if (!this._propsById[name]) {
386 		var prop = { name: name };
387 		this.userProperties.push(prop);
388 		this._propsById[name] = prop;
389 	}
390 	this._propsById[name].value = val;
391 };
392 
393 /**
394  * Gets the property.
395  * 
396  * @param	{String}	name		the name
397  * @return	{Object}	value
398  */
399 ZmZimletContext.prototype.getPropValue =
400 function(name) {
401 	return this._propsById[name] && this._propsById[name].value;
402 };
403 
404 /**
405  * Gets the property.
406  * 
407  * @param	{String}	name		the name
408  * @return	{Object}	the property
409  */
410 ZmZimletContext.prototype.getProp =
411 function(name) {
412 	return this._propsById[name];
413 };
414 
415 /**
416  * @private
417  */
418 ZmZimletContext.prototype._translateConfig =
419 function() {
420 	if (!this.config) { return; }
421 
422 	if (this.config.global && this.config.global[0]) {
423 		var prop = this.config.global[0].property;
424 		this.config.global = {};
425 		for (var i in prop) {
426 			this.config.global[prop[i].name] = prop[i]._content;
427 		}
428 	}
429 	if (this.config.local && this.config.local[0]) {
430 		var propLocal = this.config.local[0].property;
431 		this.config.local = {};
432 		for (var j in propLocal) {
433 			this.config.local[propLocal[j].name] = propLocal[j]._content;
434 		}
435 	}
436 };
437 
438 /**
439  * Gets the configuration value.
440  * 
441  * @param	{String}	name		the config key name
442  * @return	{Object}	the config value or <code>null</code> if not set
443  */
444 ZmZimletContext.prototype.getConfig =
445 function(name) {
446 
447 	var config = (this.config && this.config.length && this.config[0]) ? this.config[0] : this.config;
448 	if (!config) { return; }
449 
450 	if (config.local && config.local[name]) {
451 		return config.local[name];
452 	}
453 
454 	if (config.global && config.global[name]) {
455 		return config.global[name];
456 	}
457 
458 	return null;
459 };
460 
461 /**
462  * Gets the panel action menu.
463  * 
464  * @return	{ZmActionMenu}	the menu
465  */
466 ZmZimletContext.prototype.getPanelActionMenu =
467 function() {
468 	if (this._panelActionMenu instanceof AjxCallback) {
469 		this._panelActionMenu = this._panelActionMenu.run();
470 	}
471 	return this._panelActionMenu;
472 };
473 
474 /**
475  * Sets the panel action menu.
476  * 
477  * @param	{ZmActionMenu}	menu		the menu
478  */
479 ZmZimletContext.prototype.setPanelActionMenu =
480 function(menu) {
481 	if (menu == null || (menu instanceof ZmActionMenu) == false)
482 		return;
483 	
484 	var items = menu.getMenuItems();
485 	for (menuId in items) {
486 		var item = items[menuId];
487 		if (item.id != null || item.id != "")
488 			item.addSelectionListener(this._handleMenuItemSelected);
489 	}
490 	
491 	this._panelActionMenu = menu;
492 };
493 
494 /**
495  * @private
496  */
497 ZmZimletContext.prototype._makeMenu =
498 function(obj) {
499 	var menu = new ZmActionMenu({parent:DwtShell.getShell(window), menuItems:ZmOperation.NONE});
500 	for (var i = 0; i < obj.length; ++i) {
501 		var data = obj[i];
502 		if (!data.id) {
503 			menu.createSeparator();
504 		} else {
505 			var params = {image:data.icon, text:this.process(data.label),disImage:data.disabledIcon};
506 			var item = menu.createMenuItem(data.id, params);
507 			item.setData("xmlMenuItem", data);
508 			item.addSelectionListener(this._handleMenuItemSelected);
509 			if (data.menuItem) {
510 				item.setMenu(this._makeMenu(data.menuItem));
511 			}
512 		}
513 	}
514 	return menu;
515 };
516 
517 /**
518  * @private
519  */
520 ZmZimletContext.prototype._handleMenuItemSelected =
521 function(ev) {
522 	var data = ev.item.getData("xmlMenuItem");
523 	if (data.actionUrl) {
524 		this.handleActionUrl(data.actionUrl[0], data.canvas);
525 	} else {
526 		this.callHandler("menuItemSelected", [data.id, data, ev]);
527 	}
528 };
529 
530 /**
531  * @private
532  */
533 ZmZimletContext.prototype.process =
534 function(str, obj, props) {
535 	if (obj) {
536 		str = this.processString(str, obj);
537 	}
538 	str = this.processMessage(str); 
539 	str = this.replaceObj(ZmZimletContext.RE_SCAN_PROP, str, props || this._propsById);
540 	str = this.replaceObj(ZmZimletContext.RE_SCAN_APP, str, ZmZimletContext.APP);
541 	str = str.replace(ZmZimletContext.__RE_SCAN_SETTING, ZmZimletContext.__replaceSetting);
542 	return str;
543 };
544 
545 /**
546  * @private
547  */
548 ZmZimletContext.prototype.processString =
549 function(str, obj) {
550 	return this.replaceObj(ZmZimletContext.RE_SCAN_OBJ, str, obj);
551 };
552 
553 /**
554  * @private
555  */
556 ZmZimletContext.processMessage = function(name, str) {
557 	// i18n files load async so if not defined skip translation
558 	if (!window[name]) {
559 		DBG.println(AjxDebug.DBG2, "processMessage no messages: " + str);
560 		return str;
561 	}
562 	var props = window[name];
563 	return ZmZimletContext.replaceObj(ZmZimletContext.RE_SCAN_MSG, str, props);
564 };
565 
566 /**
567  * @private
568  */
569 ZmZimletContext.replaceObj = function(re, str, obj) {
570 	return String(str).replace(re,
571 		function(str, p1, prop) {
572 			var txt = p1;
573 			if (obj instanceof Array && obj.length > 1) {
574 				for(var i=0; i < obj.length; i++) {
575 					if(txt) {txt += ",";}
576 					var o = obj[i];
577 					if (o[prop] instanceof Object) {
578 						txt += o[prop].value;  // user prop
579 					} else {
580 						txt += o[prop];   // string
581 					}
582 				}
583 			} else {
584 				if (typeof obj[prop] != "undefined") {
585 					if (obj[prop] instanceof Object) {
586 						txt += obj[prop].value;  // user prop
587 					} else {
588 						txt += obj[prop];   // string
589 					}
590 				} else {
591 					txt += "(UNDEFINED - str '" + str + "' obj '" + obj + "')";
592 				}
593 			}
594 			return txt;
595 		});
596 };
597 
598 /**
599  * Kept for backwards compatibility.
600  * @private
601  */
602 ZmZimletContext.prototype.processMessage = function(str) {
603     return ZmZimletContext.processMessage(this.name, str);
604 };
605 
606 /**
607  * Kept for backwards compatibility.
608  * @private
609  */
610 ZmZimletContext.prototype.replaceObj = ZmZimletContext.replaceObj;
611 
612 /**
613  * @private
614  */
615 ZmZimletContext.__replaceSetting =
616 function($0, name) {
617 	return appCtxt.get(name);
618 };
619 
620 /**
621  * @private
622  */
623 ZmZimletContext.prototype.makeURL =
624 function(actionUrl, obj, props) {
625 	// All URL's to have REST substitutions
626 	var url = this.process(actionUrl.target, obj, props);
627 	var param = [];
628 	if (actionUrl.param) {
629 		var a = actionUrl.param;
630 		for (var i = 0; i < a.length; ++i) {
631 			// trim whitespace as it's almost certain that the
632 			// developer didn't intend it.
633 			var val = AjxStringUtil.trim(a[i]._content || a[i]);
634 			val = this.process(val, obj, props);
635 			param.push([ AjxStringUtil.urlEncode(a[i].name),
636 				     "=",
637 				     AjxStringUtil.urlEncode(val) ].join(""));
638 		}
639 		var startChar = actionUrl.paramStart || '?';
640 		var joinChar = actionUrl.paramJoin || '&';
641 		url = [ url, startChar, param.join(joinChar) ].join("");
642 	}
643 	return url;
644 };
645 
646 /**
647  * If there already is a paintable canvas to use, as in the case of tooltip,
648  * pass it to 'div' parameter.  otherwise a canvas (window, popup, dialog) will
649  * be created to display the contents from the url.
650  *
651  * @param actionUrl
652  * @param canvas
653  * @param obj
654  * @param div
655  * @param x
656  * @param y
657  * 
658  * @private
659  */
660 ZmZimletContext.prototype.handleActionUrl =
661 function(actionUrl, canvas, obj, div, x, y) {
662 	var url = this.makeURL(actionUrl, obj);
663 	var xslt = actionUrl.xslt && this.getXslt(actionUrl.xslt);
664 
665 	// need to use callback if the paintable canvas already exists, or if it
666 	// needs xslt transformation.
667 	if (div || xslt) {
668 		if (!div) {
669 			canvas = this.handlerObject.makeCanvas(canvas, null, x, y);
670 			div = document.getElementById("zimletCanvasDiv");
671 		}
672 		url = ZmZimletBase.PROXY + AjxStringUtil.urlComponentEncode(url);
673 		AjxRpc.invoke(null, url, null, new AjxCallback(this, this._rpcCallback, [xslt, div]), true);
674 	} else {
675 		this.handlerObject.makeCanvas(canvas, url, x, y);
676 	}
677 };
678 
679 /**
680  * @private
681  */
682 ZmZimletContext._translateZMObject =
683 function(obj) {
684 	// XXX Assumes all dragged objects are of the same type
685 	var type = obj[0] ? obj[0].toString() : obj.toString();
686 	return (ZmZimletContext._zmObjectTransformers[type])
687 		? ZmZimletContext._zmObjectTransformers[type](obj) : obj;
688 };
689 
690 /**
691  * @private
692  */
693 ZmZimletContext._zmObjectTransformers = {
694 
695 	"ZmMailMsg" : function(o) {
696 		var all = [];
697 		o = (o instanceof Array) ? o : [o];
698 		for (var i = 0; i < o.length; i++) {
699 			var ret = { TYPE: "ZmMailMsg" };
700 			var oi = o[i];
701 			ret.id			= oi.id;
702 			ret.convId		= oi.cid;
703 			ret.from		= oi.getAddresses(AjxEmailAddress.FROM).getArray();
704 			ret.to			= oi.getAddresses(AjxEmailAddress.TO).getArray();
705 			ret.cc			= oi.getAddresses(AjxEmailAddress.CC).getArray();
706 			ret.subject		= oi.subject;
707 			ret.date		= oi.date;
708 			ret.size		= oi.size;
709 			ret.fragment	= oi.fragment;
710 			ret.tags		= oi.tags;
711 			ret.unread		= oi.isUnread;
712 			ret.attachment	= oi.attachments.length > 0;
713 			ret.attlinks	= oi._attLinks || oi.getAttachmentLinks();
714 			ret.sent		= oi.isSent;
715 			ret.replied		= oi.isReplied;
716 			ret.draft		= oi.isDraft;
717 			ret.body		= ZmZimletContext._getMsgBody(oi);
718 			ret.srcObj		= oi;
719 			all[i] = ret;
720 		}
721 		if (all.length == 1) {
722 			return all[0];
723 		} else {
724 			all["TYPE"] = "ZmMailMsg";
725 			return all;
726 		}
727 	},
728 
729 	"ZmConv" : function(o) {
730 		var all = [];
731 		o = (o instanceof Array) ? o : [o];
732 		for (var i = 0; i < o.length; i++) {
733 			var oi = o[i];
734 			var ret = { TYPE: "ZmConv" };
735 			ret.id				= oi.id;
736 			ret.subject			= oi.subject;
737 			ret.date			= oi.date;
738 			ret.fragment		= oi.fragment;
739 			ret.participants	= oi.participants.getArray();
740 			ret.numMsgs			= oi.numMsgs;
741 			ret.tags			= oi.tags;
742 			ret.unread			= oi.isUnread;
743 			ret.body			= ZmZimletContext._getMsgBody(oi.getFirstHotMsg());
744 			ret.srcObj			= oi;
745 			all[i] = ret;
746 		}
747 		if (all.length == 1) {
748 			return all[0];
749 		} else {
750 			all["TYPE"] = "ZmConv";
751 			return all;
752 		}
753 	},
754 
755 	ZmContact_fields : function() {
756 		return [
757 			ZmContact.F_assistantPhone,
758 			ZmContact.F_callbackPhone,
759 			ZmContact.F_carPhone,
760 			ZmContact.F_company,
761 			ZmContact.F_companyPhone,
762 			ZmContact.F_email,
763 			ZmContact.F_email2,
764 			ZmContact.F_email3,
765 			ZmContact.F_fileAs,
766 			ZmContact.F_firstName,
767 			ZmContact.F_homeCity,
768 			ZmContact.F_homeCountry,
769 			ZmContact.F_homeFax,
770 			ZmContact.F_homePhone,
771 			ZmContact.F_homePhone2,
772 			ZmContact.F_homePostalCode,
773 			ZmContact.F_homeState,
774 			ZmContact.F_homeStreet,
775 			ZmContact.F_homeURL,
776 			ZmContact.F_jobTitle,
777 			ZmContact.F_lastName,
778 			ZmContact.F_middleName,
779 			ZmContact.F_mobilePhone,
780 			ZmContact.F_namePrefix,
781 			ZmContact.F_nameSuffix,
782 			ZmContact.F_notes,
783 			ZmContact.F_otherCity,
784 			ZmContact.F_otherCountry,
785 			ZmContact.F_otherFax,
786 			ZmContact.F_otherPhone,
787 			ZmContact.F_otherPostalCode,
788 			ZmContact.F_otherState,
789 			ZmContact.F_otherStreet,
790 			ZmContact.F_otherURL,
791 			ZmContact.F_pager,
792 			ZmContact.F_workCity,
793 			ZmContact.F_workCountry,
794 			ZmContact.F_workFax,
795 			ZmContact.F_workPhone,
796 			ZmContact.F_workPhone2,
797 			ZmContact.F_workPostalCode,
798 			ZmContact.F_workState,
799 			ZmContact.F_workStreet,
800 			ZmContact.F_workURL
801 			];
802 	},
803 
804 	"ZmContact" : function(o) {
805 		o = (o instanceof Array) ? o : [o];
806 		var all = new Array();
807 		for (var i = 0; i < o.length; i++) {
808 			var ret = { TYPE: "ZmContact" };
809 			var a = this.ZmContact_fields;
810 			if (typeof a == "function") {
811 				a = this.ZmContact_fields = a();
812 			}
813 			var attr = o[i].getAttrs();
814 			for (var j = 0; j < a.length; ++j) {
815 				ret[a[j]] = attr[a[j]];
816 			}
817 			ret.id = o[i].id;
818 			all[i] = ret;
819 		}
820 		if (all.length == 1) {
821 			return all[0];
822 		} else {
823 			all["TYPE"] = "ZmContact";
824 			return all;
825 		}
826 	},
827 
828 	"ZmFolder" : function(o) {
829 		var oi = o[0] ? o[0] : o;
830 		var ret = { TYPE: "ZmFolder" };
831 		ret.id			= oi.id;
832 		ret.name		= oi.getName();
833 		ret.path		= oi.getPath();
834 		ret.isInTrash	= oi.isInTrash();
835 		ret.unread		= oi.numUnread;
836 		ret.total		= oi.numTotal;
837 		ret.url			= oi.getRestUrl();
838 		ret.srcObj		= oi;
839 		return ret;
840 	},
841 
842 	"ZmAppt" : function(o) {
843 		var oi = o[0] ? o[0] : o;
844 		oi.getDetails();
845 		var ret = { TYPE: "ZmAppt" };
846 		ret.id				= oi.id;
847 		ret.uid				= oi.uid;
848 		ret.subject			= oi.getName();
849 		ret.startDate		= oi.startDate;
850 		ret.endDate			= oi.endDate;
851 		ret.allDayEvent		= oi.isAllDayEvent();
852 		ret.exception		= oi.isException;
853 		ret.alarm			= oi.alarm;
854 		ret.otherAttendees	= oi.otherAttendees;
855 		ret.attendees		= oi.getAttendeesText(ZmCalBaseItem.PERSON);
856 		ret.resources		= oi.getAttendeesText(ZmCalBaseItem.EQUIPMENT);
857 		ret.location		= oi.getLocation();
858 		ret.notes			= oi.getNotesPart();
859 		ret.isRecurring		= oi.isRecurring();
860 		ret.timeZone		= oi.timezone;
861 		ret.srcObj			= oi;
862 		return ret;
863 	}
864 };
865 
866 /**
867  * Gets the xslt.
868  * 
869  * @param	{String}	url		the URL
870  * @return	{AjxXslt}	the xslt
871  */
872 ZmZimletContext.prototype.getXslt =
873 function(url) {
874 	if (!this._xslt) {
875 		this._xslt = {};
876 	}
877 	var realurl = this.getUrl() + url;
878 	if (!this._xslt[realurl]) {
879 		this._xslt[realurl] = AjxXslt.createFromUrl(realurl);
880 	}
881 	return this._xslt[realurl];
882 };
883 
884 /**
885  * @private
886  */
887 ZmZimletContext.prototype._rpcCallback =
888 function(xslt, canvas, result) {
889 	var html, resp = result.xml;
890 	if (!resp) {
891 		var doc = AjxXmlDoc.createFromXml(result.text);
892 		resp = doc.getDoc();
893 	}
894 	// TODO:  instead of changing innerHTML, maybe append
895 	// the dom tree to the canvas.
896 	if (xslt) {
897 		html = xslt.transformToString(resp);
898 		// If we don't have HTML at this point, we probably have a HTML fragment.
899 		if (!html) {
900 			html = result.text;
901 		}
902 		
903 	} else {
904 		html = resp.innerHTML;
905 	}
906 	canvas.innerHTML = html;
907 };
908 
909 /**
910  * @private
911  */
912 ZmZimletContext._getMsgBody =
913 function(o) {
914 	//If message is not loaded let developer take care of it
915 	if (!o._loaded) {
916 		return "";
917 	}
918 	var part = o.getBodyPart(ZmMimeTable.TEXT_PLAIN) || o.getBodyPart(ZmMimeTable.TEXT_HTML);
919 	var content = part && part.getContent();
920 	if (content && (part.contentType == ZmMimeTable.TEXT_HTML)) {
921 		var div = document.createElement("div");
922 		div.innerHTML = content;
923 		content = AjxStringUtil.convertHtml2Text(div);
924 	}
925 	return content || "";
926 };
927