Issues (4868)

api/js/jsapi/egw_open.js (10 issues)

1
/**
2
 * EGroupware clientside API: opening of windows, popups or application entries
3
 *
4
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
5
 * @package etemplate
6
 * @subpackage api
7
 * @link http://www.egroupware.org
8
 * @author Andreas Stöckel (as AT stylite.de)
9
 * @author Ralf Becker <[email protected]>
10
 * @version $Id$
11
 */
12
13
/*egw:uses
14
	egw_core;
15
	egw_links;
16
*/
17
18
/**
19
 * @augments Class
20
 * @param {object} _egw
21
 * @param {DOMwindow} _wnd
22
 */
23
egw.extend('open', egw.MODULE_WND_LOCAL, function(_egw, _wnd)
24
{
25
	"use strict";
26
27
	/**
28
	 * Magic handling for mailto: uris using mail application.
29
	 *
30
	 * We check for open compose windows and add the address in as specified in
31
	 * the URL.  If there are no open compose windows, a new one is opened.  If
32
	 * there are more than one open compose window, we prompt for which one to
33
	 * use.
34
	 *
35
	 * The user must have set the 'Open EMail addresses in external mail program' preference
36
	 * to No, otherwise the browser will handle it.
37
	 *
38
	 * @param {String} uri
39
	 */
40
	function mailto(uri)
41
	{
42
		// Parse uri into a map
43
		var match = [], index;
44
		var mailto = uri.match(/^mailto:([^?]+)/) || [];
45
		var hashes = uri.slice(uri.indexOf('?') + 1).split('&');
46
		for(var i = 0; i < hashes.length; i++)
47
		{
48
			index = hashes[i].replace(/__AMPERSAND__/g, '&').split('=');
49
			match.push(index[0]);
50
			match[index[0]] = index[1];
51
		}
52
		if (mailto[1]) mailto[1] = mailto[1].replace(/__AMPERSAND__/g, '&');
53
		var content = {
54
			to: mailto[1] || [],
55
			cc: match['cc']	|| [],
56
			bcc: match['bcc'] || []
57
		};
58
59
		// Encode html entities in the URI, otheerwise server XSS protection wont
60
		// allow it to pass, because it may get mistaken for some forbiden tags,
61
		// e.g., "Mathias <[email protected]>" the first part of email "<mathias"
62
		// including "<" would get mistaken for <math> tag, and server will cut it off.
63
		uri = uri.replace(/</g,'&lt;').replace(/>/g,'&gt;');
64
65
		egw.openWithinWindow ("mail", "setCompose", content, {'preset[mailto]':uri}, /mail_compose.compose/);
66
67
68
		for (var index in content)
69
		{
70
			if (content[index].length > 0)
71
			{
72
				var cLen = content[index].split(',');
73
				egw.message(egw.lang('%1 email(s) added into %2', cLen.length, egw.lang(index)));
74
				return;
75
			}
76
		}
77
78
	}
79
	return {
80
		/**
81
		 * View an EGroupware entry: opens a popup of correct size or redirects window.location to requested url
82
		 *
83
		 * Examples:
84
		 * - egw.open(123,'infolog') or egw.open('infolog:123') opens popup to edit or view (if no edit rights) infolog entry 123
85
		 * - egw.open('infolog:123','timesheet','add') opens popup to add new timesheet linked to infolog entry 123
86
		 * - egw.open(123,'addressbook','view') opens addressbook view for entry 123 (showing linked infologs)
87
		 * - egw.open('','addressbook','view_list',{ search: 'Becker' }) opens list of addresses containing 'Becker'
88
		 *
89
		 * @param {string}|int|object id_data either just the id or if app=="" "app:id" or object with all data
90
		 * 	to be able to open files you need to give: (mine-)type, path or id, app2 and id2 (path=/apps/app2/id2/id"
91
		 * @param {string} app app-name or empty (app is part of id)
92
		 * @param {string} type default "edit", possible "view", "view_list", "edit" (falls back to "view") and "add"
93
		 * @param {object|string} extra extra url parameters to append as object or string
94
		 * @param {string} target target of window to open
95
		 * @param {string} target_app target application to open in that tab
96
		 * @param {boolean} _check_popup_blocker TRUE check if browser pop-up blocker is on/off, FALSE no check
97
		 * - This option only makes sense to be enabled when the open_link requested without user interaction
98
		 * @memberOf egw
99
		 */
100
		open: function(id_data, app, type, extra, target, target_app, _check_popup_blocker)
101
		{
102
			// Log for debugging purposes - special log tag 'navigation' always
103
			// goes in user log, if user log is enabled
104
			egw.debug("navigation",
105
				"egw.open(id_data=%o, app=%s, type=%s, extra=%o, target=%s, target_app=%s)",
106
				id_data,app,type,extra,target,target_app
107
			);
108
109
			var id;
110
			if(typeof target === 'undefined')
111
			{
112
				target = '_blank';
113
			}
114
			if (!app)
115
			{
116
				if (typeof id_data != 'object')
117
				{
118
					var app_id = id_data.split(':',2);
119
					app = app_id[0];
120
					id = app_id[1];
121
				}
122
				else
123
				{
124
					app = id_data.app;
125
					id = id_data.id;
126
					if(typeof id_data.type != 'undefined')
127
					{
128
						type = id_data.type;
129
					}
130
				}
131
			}
132
			else if (app != 'file')
133
			{
134
				id = id_data;
135
				id_data = { 'id': id, 'app': app, 'extra': extra };
136
			}
137
			var url;
138
			var popup;
139
			var params;
140
			if (app == 'file')
141
			{
142
				url = this.mime_open(id_data);
143
				if (typeof url == 'object')
144
				{
145
			 		if(typeof url.mime_popup != 'undefined')
146
					{
147
						popup = url.mime_popup;
148
						delete url.mime_popup;
149
					}
150
			 		if(typeof url.mime_target != 'undefined')
151
					{
152
						target = url.mime_target;
153
						delete url.mime_target;
154
					}
155
					if (typeof url.url == 'string')
156
					{
157
						url = url.url;
158
					}
159
					else
160
					{
161
						params = url;
162
						url = '/index.php';
163
					}
164
				}
165
			}
166
			else
167
			{
168
				var app_registry = this.link_get_registry(app);
169
170
				if (!app || !app_registry)
171
				{
172
					alert('egw.open() app "'+app+'" NOT defined in link registry!');
0 ignored issues
show
Debugging Code Best Practice introduced by
The alert UI element is often considered obtrusive and is generally only used as a temporary measure. Consider replacing it with another UI element.
Loading history...
173
					return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
174
				}
175
				if (typeof type == 'undefined') type = 'edit';
176
				if (type == 'edit' && typeof app_registry.edit == 'undefined') type = 'view';
177
				if (typeof app_registry[type] == 'undefined')
178
				{
179
					alert('egw.open() type "'+type+'" is NOT defined in link registry for app "'+app+'"!');
180
					return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
181
				}
182
				url = '/index.php';
183
				if(typeof app_registry[type] === 'object')
184
				{
185
					// Copy, not get a reference, or we'll change the registry
186
					params = jQuery.extend({},app_registry[type]);
187
				}
188
				else if (typeof app_registry[type] === 'string' && app_registry[type].indexOf('javascript:') === 0)
189
				{
190
					// JavaScript, just pass it on
191
					url = app_registry[type];
192
					params = {};
193
				}
194
				if (type == 'view' || type == 'edit')	// add id parameter for type view or edit
195
				{
196
					params[app_registry[type+'_id']] = id;
0 ignored issues
show
The variable params does not seem to be initialized in case typeof app_registry.type...Of("javascript:") === 0 on line 188 is false. Are you sure this can never be the case?
Loading history...
The variable id does not seem to be initialized in case app != "file" on line 132 is false. Are you sure this can never be the case?
Loading history...
197
				}
198
				else if (type == 'add' && id)	// add add_app and app_id parameters, if given for add
199
				{
200
					var app_id = id.split(':',2);
201
					params[app_registry.add_app] = app_id[0];
202
					params[app_registry.add_id] = app_id[1];
203
				}
204
205
				if (typeof extra == 'string')
206
				{
207
					url += '?'+extra;
208
				}
209
				else if (typeof extra == 'object')
210
				{
211
					jQuery.extend(params, extra);
212
				}
213
				popup = app_registry[type+'_popup'];
214
			}
215
			if(url.indexOf('javascript:') === 0)
216
			{
217
				// Add parameters into javascript
218
				url = 'javascript:var params = '+ JSON.stringify(params) + '; '+ url.substr(11);
219
			}
220
			else
221
			{
222
				url = this.link(url, params);
223
			}
224
			return this.open_link(url, target, popup, target_app, _check_popup_blocker);
0 ignored issues
show
The variable popup seems to not be initialized for all possible execution paths. Are you sure open_link handles undefined variables?
Loading history...
225
		},
226
227
		/**
228
		 * Open a link, which can be either a menuaction, a EGroupware relative url or a full url
229
		 *
230
		 * @param {string} _link menuaction, EGroupware relative url or a full url (incl. "mailto:" or "javascript:")
231
		 * @param {string} _target optional target / window name
232
		 * @param {string} _popup widthxheight, if a popup should be used
233
		 * @param {string} _target_app app-name for opener
234
		 * @param {boolean} _check_popup_blocker TRUE check if browser pop-up blocker is on/off, FALSE no check
235
		 * - This option only makes sense to be enabled when the open_link requested without user interaction
236
		 * @param {string} _mime_type if given, we check if any app has registered a mime-handler for that type and use it
237
		 */
238
		open_link: function(_link, _target, _popup, _target_app, _check_popup_blocker, _mime_type)
239
		{
240
			// Log for debugging purposes - don't use navigation here to avoid
241
			// flooding log with details already captured by egw.open()
242
			egw.debug("log",
243
				"egw.open_link(_link=%s, _target=%s, _popup=%s, _target_app=%s)",
244
				_link,_target,_popup,_target_app
245
			);
246
			//Check browser pop-up blocker
247
			if (_check_popup_blocker)
248
			{
249
				if (this._check_popupBlocker(_link, _target, _popup, _target_app)) return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
250
			}
251
			var url = _link;
252
			if (url.indexOf('javascript:') == 0)
253
			{
254
				eval(url.substr(11));
0 ignored issues
show
Security Performance introduced by
Calls to eval are slow and potentially dangerous, especially on untrusted code. Please consider whether there is another way to achieve your goal.
Loading history...
255
				return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
256
			}
257
			if (url.indexOf('mailto:') == 0)
258
			{
259
				return mailto(url);
260
			}
261
			// link is not necessary an url, it can also be a menuaction!
262
			if (url.indexOf('/') == -1 && url.split('.').length >= 3 &&
263
				!(url.indexOf('mailto:') == 0 || url.indexOf('/index.php') == 0 || url.indexOf('://') != -1))
264
			{
265
				url = "/index.php?menuaction="+url;
266
			}
267
			// append the url to the webserver url, if not already contained or empty
268
			if (url[0] == '/' && this.webserverUrl && this.webserverUrl != '/' && url.indexOf(this.webserverUrl+'/') != 0)
269
			{
270
				url = this.webserverUrl + url;
271
			}
272
			var mime_info = _mime_type ? this.get_mime_info(_mime_type) : undefined;
273
			if (mime_info && (mime_info.mime_url || mime_info.mime_data))
274
			{
275
				var data = {};
276
				for(var attr in mime_info)
277
				{
278
					switch(attr)
279
					{
280
						case 'mime_popup':
281
							_popup = mime_info.mime_popup;
282
							break;
283
						case 'mime_target':
284
							_target = mime_info.mime_target;
285
							break;
286
						case 'mime_type':
287
							data[mime_info.mime_type] = _mime_type;
288
							break;
289
						case 'mime_data':
290
							data[mime_info[attr]] = _link;
291
							break;
292
						case 'mime_url':
293
							data[mime_info[attr]] = url;
294
							break;
295
						default:
296
							data[attr] = mime_info[attr];
297
							break;
298
					}
299
				}
300
				url = egw.link('/index.php', data);
301
			}
302
			else if (mime_info)
303
			{
304
				if (mime_info.mime_popup) _popup = mime_info.mime_popup;
305
				if (mime_info.mime_target) _target = mime_info.mime_target;
306
			}
307
308
			if (_popup && _popup.indexOf('x') > 0)
309
			{
310
				var w_h = _popup.split('x');
311
				var popup_window = this.openPopup(url, w_h[0], w_h[1], _target && _target != _target_app ? _target : '_blank', _target_app, true);
312
313
				// Remember which windows are open
314
				egw().storeWindow(_target_app, popup_window);
315
316
				return popup_window;
317
			}
318
			else if ((typeof _target == 'undefined' || _target == '_self' || typeof this.link_app_list()[_target] != "undefined"))
319
			{
320
				if(_target == '_self')
321
				{
322
					// '_self' isn't allowed, but we can handle it
323
					_target = undefined;
324
				}
325
				// Use framework's link handler, if present
326
				return this.link_handler(url,_target);
327
			}
328
			else
329
			{
330
				// No mime type registered, set target properly based on browsing environment
331
				if (_target == '_browser')
332
				{
333
					_target = egwIsMobile()?'_self':'_blank';
334
				}
335
				_target = _target == '_phonecall' && _popup && _popup.indexOf('x') < 0 ? _popup:_target;
336
				return _wnd.open(url, _target);
337
			}
338
		},
339
340
		/**
341
		 * Open a (centered) popup window with given size and url
342
		 *
343
		 * @param {string} _url
344
		 * @param {number} _width
345
		 * @param {number} _height
346
		 * @param {string} _windowName or "_blank"
347
		 * @param {string|boolean} _app app-name for framework to set correct opener or false for current app
348
		 * @param {boolean} _returnID true: return window, false: return undefined
349
		 * @param {string} _status "yes" or "no" to display status bar of popup
350
		 * @param {boolean} _skip_framework
351
		 * @returns {DOMWindow|undefined}
352
		 */
353
		openPopup: function(_url, _width, _height, _windowName, _app, _returnID, _status, _skip_framework)
354
		{
355
			// Log for debugging purposes
356
			egw.debug("navigation", "openPopup(%s, %s, %s, %o, %s, %s)",_url,_windowName,_width,_height,_status,_app);
357
358
			if (_height == 'availHeight') _height = this.availHeight();
359
360
			// if we have a framework and we use mobile template --> let framework deal with opening popups
361
			if (!_skip_framework && _wnd.framework)
362
			{
363
				return _wnd.framework.openPopup(_url, _width, _height, _windowName, _app, _returnID, _status, _wnd);
364
			}
365
366
			if (typeof(_app) == 'undefined') _app = false;
367
			if (typeof(_returnID) == 'undefined') _returnID = false;
368
369
			var $wnd = jQuery(_wnd.top);
370
			var positionLeft = ($wnd.outerWidth()/2)-(_width/2)+_wnd.screenX;
371
			var positionTop  = ($wnd.outerHeight()/2)-(_height/2)+_wnd.screenY;
372
373
			// IE fails, if name contains eg. a dash (-)
374
			if (navigator.userAgent.match(/msie/i)) _windowName = !_windowName ? '_blank' : _windowName.replace(/[^a-zA-Z0-9_]+/,'');
375
376
			var windowID = _wnd.open(_url, _windowName || '_blank', "width=" + _width + ",height=" + _height +
377
				",screenX=" + positionLeft + ",left=" + positionLeft + ",screenY=" + positionTop + ",top=" + positionTop +
378
				",location=no,menubar=no,directories=no,toolbar=no,scrollbars=yes,resizable=yes,status="+_status);
379
380
			// inject egw object
381
			if (windowID) windowID.egw = _wnd.egw;
382
383
			// returning something, replaces whole window in FF, if used in link as "javascript:egw_openWindowCentered2()"
384
			if (_returnID !== false) return windowID;
385
		},
386
387
		/**
388
		 * Get available height of screen
389
		 */
390
		availHeight: function()
391
		{
392
			return screen.availHeight < screen.height ?
393
				(navigator.userAgent.match(/windows/ig)? screen.availHeight -100:screen.availHeight) // Seems chrome not counting taskbar in available height
394
				: screen.height - 100;
395
		},
396
397
		/**
398
		 * Use frameworks (framed template) link handler to open a url
399
		 *
400
		 * @param {string} _url
401
		 * @param {string} _target
402
		 */
403
		link_handler: function(_url, _target)
404
		{
405
			// if url is supposed to open in admin, use admins loader to open it in it's own iframe
406
			// (otherwise there's no tree and sidebox!)
407
			if (_target === 'admin' && !_url.match(/menuaction=admin\.admin_ui\.index/))
408
			{
409
				_url = _url.replace(/menuaction=([^&]+)/, 'menuaction=admin.admin_ui.index&load=$1');
410
			}
411
			if (_wnd.framework)
412
			{
413
				_wnd.framework.linkHandler(_url, _target);
414
			}
415
			else
416
			{
417
				_wnd.location.href = _url;
418
			}
419
		},
420
421
		/**
422
		 * Close current window / popup
423
		 */
424
		close: function()
425
		{
426
			if (_wnd.framework && typeof _wnd.framework.popup_close == "function")
427
			{
428
				_wnd.framework.popup_close(_wnd);
429
			}
430
			else
431
			{
432
				_wnd.close();
433
			}
434
		},
435
436
		/**
437
		 * Check if browser pop-up blocker is on/off
438
		 *
439
		 * @param {string} _link menuaction, EGroupware relative url or a full url (incl. "mailto:" or "javascript:")
440
		 * @param {string} _target optional target / window name
441
		 * @param {string} _popup widthxheight, if a popup should be used
442
		 * @param {string} _target_app app-name for opener
443
		 *
444
		 * @return boolean returns false if pop-up blocker is off
445
		 * - returns true if pop-up blocker is on,
446
		 * - and re-call the open_link with provided parameters, after user interaction.
447
		 */
448
		_check_popupBlocker: function(_link, _target, _popup, _target_app)
449
		{
450
			var popup = window.open("","",'top='+(screen.height/2)+',left='+(screen.width/2)+',width=1,height=1,menubar=no,resizable=yes,scrollbars=yes,status=no,toolbar=no,dependent=yes');
451
452
			if (!popup||popup == 'undefined'||popup == null)
453
			{
454
				et2_dialog.show_dialog(function(){
455
					window.egw.open_link(_link, _target, _popup, _target_app);
456
				},egw.lang("The browser popup blocker is on. Please click on OK button to see the pop-up.\n\nIf you would like to not see this message for the next time, allow your browser pop-up blocker to open popups from %1",window.location.hostname) ,
457
				"Popup Blocker Warning",{},et2_dialog.BUTTONS_OK,et2_dialog.WARNING_MESSAGE);
458
				return true;
459
			}
460
			else
461
			{
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
462
				popup.close();
463
				return false;
464
			}
465
		},
466
467
		/**
468
		 * This function helps to append content/ run commands into an already
469
		 * opened popup window. Popup winodws now are getting stored in framework
470
		 * object and can be retrived/closed from framework.
471
		 *
472
		 * @param {string} _app name of application to be requested its popups
473
		 * @param {string} _method application method implemented in app.js
474
		 * @param {object} _content content to be passed to method
475
		 * @param {string|object} _extra url or object of extra
476
		 * @param {regex} _regexp regular expression to get specific popup with matched url
477
		 */
478
		openWithinWindow: function (_app, _method, _content, _extra, _regexp)
479
		{
480
			var popups = window.framework.popups_get(_app, _regexp);
481
482
			var openUp = function (_app, _extra) {
483
484
				var len = 0;
485
				if (typeof _extra == "string")
486
				{
487
					len = _extra.length;
488
				}
489
				else if (typeof _extra == "object")
490
				{
491
					for (var i in _extra)
492
					{
493
						if (jQuery.isArray(_extra[i]))
494
						{
495
							var tmp = '';
496
							for (var j in _extra[i])
497
							{
498
								tmp += i+'[]='+_extra[i][j]+'&';
499
500
							}
501
							len += tmp.length;
502
						}
503
						else if(_extra[i])
504
						{
505
							len += _extra[i].length;
506
						}
507
					}
508
				}
509
510
				// Accoring to microsoft, IE 10/11 can only accept a url with 2083 caharacters
511
				// therefore we need to send request to compose window with POST method
512
				// instead of GET. We create a temporary <Form> and will post emails.
513
				// ** WebServers and other browsers also have url length limit:
514
				// Firefox:~ 65k, Safari:80k, Chrome: 2MB, Apache: 4k, Nginx: 4k
515
				if (len > 2083)
516
				{
517
					var popup = egw.open('','mail','add','','compose__','mail');
518
					var $tmpForm = jQuery(document.createElement('form'));
519
					var $tmpSubmitInput = jQuery(document.createElement('input')).attr({type:"submit"});
520
					for (var i in _extra)
521
					{
522
						if (jQuery.isArray(_extra[i]))
523
						{
524
							$tmpForm.append(jQuery(document.createElement('input')).attr({name:i, type:"text", value: JSON.stringify(_extra[i])}));
525
						}
526
						else
527
						{
528
							$tmpForm.append(jQuery(document.createElement('input')).attr({name:i, type:"text", value: _extra[i]}));
529
						}
530
					}
531
532
					// Set the temporary form's attributes
533
					$tmpForm.attr({target:popup.name, action:"index.php?menuaction=mail.mail_compose.compose", method:"post"})
534
							.append($tmpSubmitInput).appendTo('body');
535
					$tmpForm.submit();
536
					// Remove the form after submit
537
					$tmpForm.remove();
538
				}
539
				else
540
				{
541
					egw.open('', _app, 'add', _extra, _app, _app);
542
				}
543
			};
544
			for(var i = 0; i < popups.length; i++)
545
			{
546
				if(popups[i].closed)
547
				{
548
					window.framework.popups_grabage_collector();
549
				}
550
			}
551
552
			if(popups.length == 1)
553
			{
554
				try {
555
					popups[0].app[_app][_method](popups[0], _content);
556
				}
557
				catch(e) {
558
					window.setTimeout(function() {
559
						openUp(_app, _extra);
560
					});
561
				}
562
			}
563
			else if (popups.length > 1)
564
			{
565
				var buttons = [
566
					{text: this.lang("Add"), id: "add", "class": "ui-priority-primary", "default": true},
567
					{text: this.lang("Cancel"), id:"cancel"}
568
				];
569
				var c = [];
570
				for(var i = 0; i < popups.length; i++)
571
				{
572
					c.push({label:popups[i].document.title || this.lang(_app), index:i});
573
				}
574
				et2_createWidget("dialog",
575
				{
576
					callback: function(_button_id, _value) {
577
						if (_value && _value.grid)
578
						{
579
							switch (_button_id)
580
							{
581
								case "add":
582
									popups[_value.grid.index].app[_app][_method](popups[_value.grid.index], _content);
583
									return;
584
								case "cancel":
585
							}
586
						}
587
					},
588
					title: this.lang("Select an opened dialog"),
589
					buttons: buttons,
590
					value:{content:{grid:c}},
591
					template: this.webserverUrl+'/api/templates/default/promptOpenedDialog.xet?1',
592
					resizable: false
593
				}, et2_dialog._create_parent(this.app_name()));
594
			}
595
			else
596
			{
597
				openUp(_app, _extra);
598
			}
599
		}
600
	};
601
});
602
603
604
// Add protocol handler as an option if mail handling is not forced so mail can handle mailto:
605
/* Not working consistantly yet
606
jQuery(function() {
607
try {
608
	if(egw.user('apps').mail && (egw.preference('force_mailto','addressbook')||true) != '0')
609
	{
610
		var params = egw.link_get_registry('mail','add');
611
		if(params)
612
		{
613
			params['preset[mailto]'] = ''; // %s, but egw.link will encode it
614
			navigator.registerProtocolHandler("mailto",egw.link('/index.php', params)+'%s', egw.lang('mail'));
615
		}
616
	}
617
} catch (e) {}
618
});
619
*/
620