1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2004, 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) 2004, 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 command handler class. The following commands are supported:
 27  *
 28  * 		$set:debug {1,2,3}					set debug level to 1, 2, or 3
 29  * 		$set:debug t						toggle debug timing on/off
 30  * 		$set:debugtrace {on,off}			turn offline debug trace on/off
 31  * 		$set:instant_notify					show whether instant notify is on or off
 32  * 		$set:instant_notify {on,off}		turn instant notify on/off
 33  *		$set:poll [N]						set poll interval to N seconds (unless doing instant notify)
 34  * 		$set:noop							send a poll (get notifications)
 35  * 		$set:rr								refresh reminders
 36  * 		$set:rh								run reminder housekeeping
 37  * 		$set:toast							show sample toast messages
 38  * 		$set:get version					show client version
 39  * 		$set:expire							expire session; refresh block will come on next response
 40  * 		$set:refresh						force immediate return of a refresh block
 41  * 		$set:relogin						logout the user
 42  * 		$set:alert							issue a test sound alert
 43  * 		$set:alert {browser,desktop,app} N	issue a test alert in given context in N seconds
 44  * 		$set:leak {begin,report,end}		manage leak detection
 45  * 		$set:tabs							show tab groups (in debug window)
 46  * 		$set:ymid [id]						set Yahoo IM user to id
 47  * 		$set:log [type]						dump log contents for type
 48  * 		$set:log [type]	[size]				set number of msgs to keep for type
 49  * 		$set:compose						compose msg based on mailto: in query string
 50  * 		$set:error							show error dialog
 51  * 		$set:modify [setting] [value]		set setting to value, then optionally restart
 52  * 		$set:clearAutocompleteCache			clear contacts autocomplete cache
 53  *
 54  * TODO: should probably I18N the alert messages
 55  */
 56 
 57 /**
 58  * Creates the client. command handler
 59  * @class
 60  * This class represents a client command handler.
 61  */
 62 ZmClientCmdHandler = function() {
 63 	this._settings = {};
 64 	this._dbg = window.DBG;	// trick to fool minimizer regex
 65 };
 66 
 67 /**
 68  * Executes the command.
 69  * 
 70  * @param	{String}	cmdStr		the command
 71  * @param	{ZmSearchController}	searchController	the search controller
 72  * @param	{Object}	[cmdArg1]		command arguments
 73  */
 74 ZmClientCmdHandler.prototype.execute =
 75 function(cmdStr, searchController) {
 76 
 77 	if (!cmdStr) { return; }
 78 
 79 	cmdStr = AjxStringUtil.trim(cmdStr, true);
 80 	
 81 	if (cmdStr == "") { return; }
 82 	
 83 	var argv = cmdStr.split(/\s/);
 84 	var arg0 = argv[0];
 85 
 86 	if (arg0 != "modify") {
 87 		cmdStr = cmdStr.toLowerCase();
 88 	}
 89 
 90 	var func = this["execute_" + arg0];
 91 	if (func) {
 92 		var args = [].concat(cmdStr, searchController, argv);
 93 		return func.apply(this, args);
 94 	}
 95 };
 96 
 97 /**
 98  * @private
 99  */
100 ZmClientCmdHandler.prototype._alert =
101 function(msg, level) {
102 	appCtxt.setStatusMsg(msg, level);
103 };
104 
105 /**
106  * Executes the command with debug.
107  * 
108  * @param	{String}	cmdStr		the command
109  * @param	{ZmSearchController}	searchController	the search controller
110  * @param	{Object}	[cmdArg1]		command arguments
111  */
112 ZmClientCmdHandler.prototype.execute_debug =
113 function(cmdStr, searchController, cmdName, cmdArg1 /* ..., cmdArgN */) {
114 	if (!cmdArg1 || !this._dbg) { return; }
115 	if (cmdArg1 == "t") {
116 		var on = this._dbg._showTiming;
117 		var newState = on ? "off" : "on";
118 		this._alert("Turning timing info " + newState);
119 		this._dbg.showTiming(!on);
120 	} else {
121 		this._dbg.setDebugLevel(cmdArg1);
122 		this._alert("Setting debug level to: " + cmdArg1);
123 	}
124 };
125 
126 /**
127  * Executes the command with debug trace.
128  * 
129  * @param	{String}	cmdStr		the command
130  * @param	{ZmSearchController}	searchController	the search controller
131  * @param	{Object}	[cmdArg1]		command arguments
132  */
133 ZmClientCmdHandler.prototype.execute_debugtrace =
134 function(cmdStr, searchController, cmdName, cmdArg1 /* ..., cmdArgN */) {
135 	if (!cmdArg1) return;
136 
137 	var val;
138 	if (cmdArg1 == "on") {
139 		val = true;
140 	} else if (cmdArg1 == "off") {
141 		val = false;
142 	}
143 
144 	if (val != undefined) {
145 		appCtxt.set(ZmSetting.OFFLINE_DEBUG_TRACE, val, null, null, true);
146 		this._alert("Debug trace is " + cmdArg1);
147 	}
148 };
149 
150 /**
151  * Executes the instant notify "ON" command.
152  * 
153  * @param	{String}	cmdStr		the command
154  * @param	{ZmSearchController}	searchController	the search controller
155  * @param	{Object}	[cmdArg1]		command arguments
156  */
157 ZmClientCmdHandler.prototype.execute_instant_notify =
158 function(cmdStr, searchController, cmdName, cmdArg1 /* ..., cmdArgN */) {
159 	if (typeof cmdArg1 == "undefined") {
160 		this._alert("Instant notify is "+ (appCtxt.getAppController().getInstantNotify() ? "ON" : "OFF"));
161 	} else {
162 		var on = cmdArg1 && (cmdArg1.toLowerCase() == "on");
163 		this._alert("Set instant notify to "+ (on ? "ON" : "OFF"));
164 		appCtxt.getAppController().setInstantNotify(on);
165 	}
166 };
167 
168 /**
169  * Executes the poll interval command.
170  * 
171  * @param	{String}	cmdStr		the command
172  * @param	{ZmSearchController}	searchController	the search controller
173  * @param	{Object}	[cmdArg1]		command arguments
174  */
175 ZmClientCmdHandler.prototype.execute_poll =
176 function(cmdStr, searchController, cmdName, cmdArg1 /* ..., cmdArgN */) {
177 	if (!cmdArg1) return;
178 	appCtxt.set(ZmSetting.POLLING_INTERVAL, cmdArg1);
179 	var pi = appCtxt.get(ZmSetting.POLLING_INTERVAL); // LDAP time format converted to seconds
180 	if (appCtxt.getAppController().setPollInterval(true)) {
181 		this._alert("Set polling interval to " + pi + " seconds");
182 	} else {
183 		this._alert("Ignoring polling interval b/c we are in Instant_Polling mode ($set:instant_notify 0|1)");
184 	}
185 };
186 
187 /**
188  * Executes the no op command.
189  * 
190  * @param	{String}	cmdStr		the command
191  * @param	{ZmSearchController}	searchController	the search controller
192  * @param	{Object}	[cmdArg1]		command arguments
193  */
194 ZmClientCmdHandler.prototype.execute_noop =
195 function(cmdStr, searchController, cmdName, cmdArg1 /* ..., cmdArgN */) {
196 	appCtxt.getAppController().sendNoOp();
197 	this._alert("Sent NoOpRequest");
198 };
199 
200 /**
201  * Executes the reminder refresh command.
202  * 
203  * @param	{String}	cmdStr		the command
204  * @param	{ZmSearchController}	searchController	the search controller
205  * @param	{Object}	[cmdArg1]		command arguments
206  */
207 ZmClientCmdHandler.prototype.execute_rr =
208 function(cmdStr, searchController, cmdName, cmdArg1 /* ..., cmdArgN */) {
209 	appCtxt.getApp(ZmApp.CALENDAR).getReminderController().refresh();
210 };
211 
212 /**
213  * Executes the reminder housekeeping command.
214  * 
215  * @param	{String}	cmdStr		the command
216  * @param	{ZmSearchController}	searchController	the search controller
217  * @param	{Object}	[cmdArg1]		command arguments
218  */
219 ZmClientCmdHandler.prototype.execute_rh =
220 function(cmdStr, searchController, cmdName, cmdArg1 /* ..., cmdArgN */) {
221 	appCtxt.getApp(ZmApp.CALENDAR).getReminderController()._housekeepingAction();
222 };
223 
224 /**
225  * Executes the toast command.
226  * 
227  * @param	{String}	cmdStr		the command
228  * @param	{ZmSearchController}	searchController	the search controller
229  * @param	{Object}	[cmdArg1]		command arguments
230  */
231 ZmClientCmdHandler.prototype.execute_toast =
232 function(cmdStr, searchController, cmdName, cmdArg1 /* ..., cmdArgN */) {
233 	appCtxt.setStatusMsg("Your options have been saved.", ZmStatusView.LEVEL_INFO);
234 	appCtxt.setStatusMsg("Unable to save options.", ZmStatusView.LEVEL_WARNING);
235 	appCtxt.setStatusMsg("Message not sent.", ZmStatusView.LEVEL_CRITICAL);
236 };
237 
238 /**
239  * Executes the get version/release/date command.
240  * 
241  * @param	{String}	cmdStr		the command
242  * @param	{ZmSearchController}	searchController	the search controller
243  * @param	{Object}	[cmdArg1]		command arguments
244  */
245 ZmClientCmdHandler.prototype.execute_get =
246 function(cmdStr, searchController, cmdName, cmdArg1 /* ..., cmdArgN */) {
247 	if (cmdArg1 && cmdArg1 == "version") {
248 		alert("Client Information\n\n" +
249 			  "Client Version: " + appCtxt.get(ZmSetting.CLIENT_VERSION) + "\n" +
250 			  "Client Release: " + appCtxt.get(ZmSetting.CLIENT_RELEASE) + "\n" +
251 			  "    Build Date: " + appCtxt.get(ZmSetting.CLIENT_DATETIME));
252 	}
253 };
254 
255 /**
256  * Executes the expire command.
257  *
258  * @param	{String}	cmdStr		the command
259  * @param	{ZmSearchController}	searchController	the search controller
260  * @param	{Object}	[cmdArg1]		command arguments
261  */
262 ZmClientCmdHandler.prototype.execute_expire =
263 function(cmdStr, searchController, cmdName, cmdArg1 /* ..., cmdArgN */) {
264 	ZmCsfeCommand.clearSessionId();
265 	this._alert("Session expired");
266 };
267 
268 /**
269  * Executes the refresh command.
270  * 
271  * @param	{String}	cmdStr		the command
272  * @param	{ZmSearchController}	searchController	the search controller
273  * @param	{Object}	[cmdArg1]		command arguments
274  */
275 ZmClientCmdHandler.prototype.execute_refresh = 
276 function(cmdStr, searchController, cmdName, cmdArg1 /* ..., cmdArgN */) {
277 	ZmCsfeCommand.clearSessionId();
278 	appCtxt.getAppController().sendNoOp();
279 };
280 
281 /**
282  * Executes the re-login command.
283  * 
284  * @param	{String}	cmdStr		the command
285  * @param	{ZmSearchController}	searchController	the search controller
286  * @param	{Object}	[cmdArg1]		command arguments
287  * @param	{Object}	[cmdArg2]		command arguments
288  */
289 ZmClientCmdHandler.prototype.execute_relogin =
290 function(cmdStr, searchController, cmdName, cmdArg1, cmdArg2 /* ..., cmdArgN */) {
291 	ZmCsfeCommand.clearAuthToken();
292 	appCtxt.getAppController().sendNoOp();
293 };
294 
295 /**
296  * Executes the alert command.
297  * 
298  * @param	{String}	cmdStr		the command
299  * @param	{ZmSearchController}	searchController	the search controller
300  * @param	{Object}	[cmdArg1]		command arguments
301  * @param	{Object}	[cmdArg2]		command arguments
302  */
303 ZmClientCmdHandler.prototype.execute_alert =
304 function(cmdStr, searchController, cmdName, cmdArg1, cmdArg2 /* ..., cmdArgN */) {
305 	//  $set:alert [sound/browser/app] [delay in seconds]
306 	function doIt() {
307 		if (cmdArg1 == "browser") {
308 			AjxDispatcher.require("Alert");
309 			ZmBrowserAlert.getInstance().start("Alert Test!");
310 		} else if (cmdArg1 == "desktop") {
311 			AjxDispatcher.require("Alert");
312 			ZmDesktopAlert.getInstance().start("Title!", "Message!");
313 		} else if (cmdArg1 == "app") {
314 			appCtxt.getApp(ZmApp.MAIL).startAlert();
315 		} else {
316 			AjxDispatcher.require("Alert");
317 			ZmSoundAlert.getInstance().start();
318 		}
319 	}
320 	setTimeout(doIt, Number(cmdArg2) * 1000);
321 };
322 
323 /**
324  * Executes the leak detector command.
325  * 
326  * @param	{String}	cmdStr		the command
327  * @param	{ZmSearchController}	searchController	the search controller
328  * @param	{Object}	[cmdArg1]		command arguments
329  */
330 ZmClientCmdHandler.prototype.execute_leak =
331 function(cmdStr, searchController, cmdName, cmdArg1 /* ..., cmdArgN */) {
332 	AjxPackage.require("ajax.debug.AjxLeakDetector");
333 	if (!window.AjxLeakDetector) {
334 		this._alert("AjxLeakDetector could not be loaded", ZmStatusView.LEVEL_WARNING);
335 	} else {
336 		var leakResult = AjxLeakDetector.execute(cmdArg1);
337 		this._alert(leakResult.message, leakResult.success ? ZmStatusView.LEVEL_INFO : ZmStatusView.LEVEL_WARNING);
338 	}
339 };
340 
341 /**
342  * Executes the tabs command.
343  * 
344  * @param	{String}	cmdStr		the command
345  * @param	{ZmSearchController}	searchController	the search controller
346  * @param	{Object}	[cmdArg1]		command arguments
347  */
348 ZmClientCmdHandler.prototype.execute_tabs =
349 function(cmdStr, searchController, cmdName, cmdArg1 /* ..., cmdArgN */) {
350 	appCtxt.getRootTabGroup().dump(AjxDebug.DBG1);
351 };
352 
353 /**
354  * Executes the Yahoo! IM command.
355  * 
356  * @param	{String}	cmdStr		the command
357  * @param	{ZmSearchController}	searchController	the search controller
358  * @param	{Object}	[cmdArg1]		command arguments
359  */
360 ZmClientCmdHandler.prototype.execute_ymid =
361 function(cmdStr, searchController, cmdName, cmdArg1 /* ..., cmdArgN */) {
362 	var settings = appCtxt.getSettings(),
363 		setting = settings.getSetting(ZmSetting.IM_YAHOO_ID);
364 	setting.setValue(cmdArg1 || "");
365 	settings.save([setting]);
366 	this._alert("Done");
367 };
368 
369 /**
370  * Executes the log command.
371  * 
372  * @param	{String}	cmdStr		the command
373  * @param	{ZmSearchController}	searchController	the search controller
374  * @param	{Object}	[cmdArg1]		command arguments
375  */
376 ZmClientCmdHandler.prototype.execute_log =
377 function(cmdStr, searchController, cmdName, cmdArg1, cmdArg2 /* ..., cmdArgN */) {
378 
379 	var type = cmdArg1;
380 	if (cmdArg2 != null) {
381 		var size = parseInt(cmdArg2);
382 		if (!isNaN(size)) {
383 			AjxDebug.BUFFER_MAX[type] = size;
384 			this._alert("Debug log size for '" + type + "' set to " + size);
385 		}
386 	}
387 	else {
388 		appCtxt.getDebugLogDialog().popup(AjxDebug.getDebugLog(type), type);
389 	}
390 };
391 
392 /**
393  * Executes the compose command.
394  * 
395  * @param	{String}	cmdStr		the command
396  * @param	{ZmSearchController}	searchController	the search controller
397  * @param	{Object}	[cmdArg1]		command arguments
398  */
399 ZmClientCmdHandler.prototype.execute_compose =
400 function(cmdStr, searchController, cmdName, cmdArg1 /* ..., cmdArgN */) {
401 	var mailApp = appCtxt.getApp(ZmApp.MAIL);
402 	var idx = (location.search.indexOf("mailto"));
403 	if (idx >= 0) {
404 		var query = "to=" + decodeURIComponent(location.search.substring(idx+7));
405 		query = query.replace(/\?/g, "&");
406 
407 		mailApp._showComposeView(null, query);
408 		return true;
409 	}
410 };
411 
412 /**
413  * Executes the error command.
414  * 
415  * @param	{String}	cmdStr		the command
416  * @param	{ZmSearchController}	searchController	the search controller
417  * @param	{Object}	[cmdArg1]		command arguments
418  * @param	{Object}	[cmdArg2]		command arguments
419  */
420 ZmClientCmdHandler.prototype.execute_error =
421 function(cmdStr, searchController, cmdName, cmdArg1, cmdArg2 /* ..., cmdArgN */) {
422 	var errDialog = appCtxt.getErrorDialog();
423 	errDialog.setMessage("Error Message!", "Details!!", DwtMessageDialog.WARNING_STYLE);
424 	errDialog.popup();
425 
426 };
427 
428 /**
429  * Executes the modify command.
430  * 
431  * @param	{String}	cmdStr		the command
432  * @param	{ZmSearchController}	searchController	the search controller
433  * @param	{Object}	[cmdArg1]		command arguments
434  * @param	{Object}	[cmdArg2]		command arguments
435  */
436 ZmClientCmdHandler.prototype.execute_modify =
437 function(cmdStr, searchController, cmdName, cmdArg1, cmdArg2 /* ..., cmdArgN */) {
438 	var settings = appCtxt.getSettings();
439 	var id = cmdArg1 && settings.getSettingByName(cmdArg1);
440 	if (id) {
441 		var setting = settings.getSetting(id);
442 		setting.setValue(cmdArg2 || setting.getDefaultValue());
443 		if (ZmSetting.IS_IMPLICIT[setting.id]) {
444 			appCtxt.accountList.saveImplicitPrefs();
445 		} else {
446 			settings.save([setting]);
447 		}
448 	}
449 
450 	settings._showConfirmDialog(ZmMsg.accountChangeRestart, settings._refreshBrowserCallback.bind(settings));
451 };
452 
453 /**
454  * Executes the clearAutocompleteCache command.
455  *
456  * @param	{String}	cmdStr		the command
457  * @param	{ZmSearchController}	searchController	the search controller
458  * @param	{Object}	[cmdArg1]		command arguments
459  * @param	{Object}	[cmdArg2]		command arguments
460  */
461 ZmClientCmdHandler.prototype.execute_clearAutocompleteCache =
462 function(cmdStr, searchController, cmdName, cmdArg1, cmdArg2 /* ..., cmdArgN */) {
463 	appCtxt.clearAutocompleteCache(ZmAutocomplete.AC_TYPE_CONTACT);
464 	this._alert("Contacts autocomplete cache cleared");
465 };
466 
467 /**
468  * Executes the getCharWidth command.
469  *
470  * @param	{String}	cmdStr		the command
471  * @param	{ZmSearchController}	searchController	the search controller
472  * @param	{Object}	[cmdArg1]		command arguments
473  * @param	{Object}	[cmdArg2]		command arguments
474  */
475 ZmClientCmdHandler.prototype.execute_getCharWidth =
476 function(cmdStr, searchController, cmdName, cmdArg1, cmdArg2 /* ..., cmdArgN */) {
477 	var cla = appCtxt.getApp(ZmApp.CONTACTS).getContactList().getArray();
478 	for (var i = 0; i < cla.length; i++) {
479 		ZmClientCmdHandler._testWidth(cla[i]._attrs["firstLast"] || cla[i]._attrs["company"] || "");
480 	}
481 	var text = [];
482 	text.push("Avg char width: " + ZmClientCmdHandler.WIDTH / ZmClientCmdHandler.CHARS);
483 	text.push("Avg bold char width: " + ZmClientCmdHandler.BWIDTH / ZmClientCmdHandler.CHARS);
484 	var w = ZmClientCmdHandler._testWidth(AjxStringUtil.ELLIPSIS);
485 	text.push("Ellipsis width: " + w.w);
486 	w = ZmClientCmdHandler._testWidth(", ");
487 	text.push("Comma + space width: " + w.w);
488 	alert(text.join("\n"));
489 };
490 
491 ZmClientCmdHandler.CHARS = 0;
492 ZmClientCmdHandler.WIDTH = 0;
493 ZmClientCmdHandler.BWIDTH = 0;
494 
495 ZmClientCmdHandler._testWidth =
496 function(str) {
497 	var div = document.createElement("DIV");
498 	div.style.position = Dwt.ABSOLUTE_STYLE;
499 	var shellEl = appCtxt.getShell().getHtmlElement();
500 	shellEl.appendChild(div);
501 	Dwt.setLocation(div, Dwt.LOC_NOWHERE, Dwt.LOC_NOWHERE);
502 	div.innerHTML = str;
503 	var size = Dwt.getSize(div);
504 	ZmClientCmdHandler.CHARS += str.length;
505 	ZmClientCmdHandler.WIDTH += size.x;
506 	div.style.fontWeight = "bold";
507 	var bsize = Dwt.getSize(div);
508 	ZmClientCmdHandler.BWIDTH += bsize.x;
509 	shellEl.removeChild(div);
510 	return {w:size.x, bw:bsize.x};
511 };
512