Issues (1798)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

core/js/js.js (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
/**
2
 * Disable console output unless DEBUG mode is enabled.
3
 * Add
4
 *      'debug' => true,
5
 * To the definition of $CONFIG in config/config.php to enable debug mode.
6
 * The undefined checks fix the broken ie8 console
7
 */
8
9
/* global oc_isadmin */
10
11
var oc_debug;
12
var oc_webroot;
13
14
var oc_requesttoken = document.getElementsByTagName('head')[0].getAttribute('data-requesttoken');
15
16
window.oc_config = window.oc_config || {};
17
18
if (typeof oc_webroot === "undefined") {
19
	oc_webroot = location.pathname;
20
	var pos = oc_webroot.indexOf('/index.php/');
21
	if (pos !== -1) {
22
		oc_webroot = oc_webroot.substr(0, pos);
23
	}
24
	else {
25
		oc_webroot = oc_webroot.substr(0, oc_webroot.lastIndexOf('/'));
26
	}
27
}
28
29
/**
30
 * Sanitizes a HTML string by replacing all potential dangerous characters with HTML entities
31
 * @param {string} s String to sanitize
32
 * @return {string} Sanitized string
33
 */
34
function escapeHTML(s) {
35
	return s.toString().split('&').join('&amp;').split('<').join('&lt;').split('>').join('&gt;').split('"').join('&quot;').split('\'').join('&#039;');
36
}
37
38
/**
39
 * Get the path to download a file
40
 * @param {string} file The filename
41
 * @param {string} dir The directory the file is in - e.g. $('#dir').val()
42
 * @return {string} Path to download the file
43
 * @deprecated use Files.getDownloadURL() instead
44
 */
45
function fileDownloadPath(dir, file) {
46
	return OC.filePath('files', 'ajax', 'download.php') + '?files=' + encodeURIComponent(file) + '&dir=' + encodeURIComponent(dir);
47
}
48
49
/** @namespace */
50
var OC = {
51
	PERMISSION_CREATE: 4,
52
	PERMISSION_READ: 1,
53
	PERMISSION_UPDATE: 2,
54
	PERMISSION_DELETE: 8,
55
	PERMISSION_SHARE: 16,
56
	PERMISSION_ALL: 31,
57
	TAG_FAVORITE: '_$!<Favorite>!$_',
58
	/* jshint camelcase: false */
59
	/**
60
	 * Relative path to ownCloud root.
61
	 * For example: "/owncloud"
62
	 *
63
	 * @type string
64
	 *
65
	 * @deprecated since 8.2, use OC.getRootPath() instead
66
	 * @see OC#getRootPath
67
	 */
68
	webroot: oc_webroot,
69
70
	/**
71
	 * Capabilities
72
	 *
73
	 * @type array
74
	 */
75
	_capabilities: window.oc_capabilities || null,
76
77
	appswebroots: (typeof oc_appswebroots !== 'undefined') ? oc_appswebroots : false,
78
	/**
79
	 * Currently logged in user or null if none
80
	 *
81
	 * @type String
82
	 * @deprecated use {@link OC.getCurrentUser} instead
83
	 */
84
	currentUser: (typeof oc_user !== 'undefined') ? oc_user.uid : false,
85
	config: window.oc_config,
86
	appConfig: window.oc_appconfig || {},
87
	theme: window.oc_defaults || {},
88
	coreApps: ['', 'admin', 'log', 'core/search', 'settings', 'core', '3rdparty'],
89
	requestToken: oc_requesttoken,
90
	menuSpeed: 50,
91
	currentTheme: window.theme || {},
92
93
	/**
94
	 * Get an absolute url to a file in an app
95
	 * @param {string} app the id of the app the file belongs to
96
	 * @param {string} file the file path relative to the app folder
97
	 * @return {string} Absolute URL to a file
98
	 */
99
	linkTo: function (app, file) {
100
		return OC.filePath(app, '', file);
101
	},
102
103
	/**
104
	 * Creates a relative url for remote use
105
	 * @param {string} service id
106
	 * @return {string} the url
107
	 */
108
	linkToRemoteBase: function (service) {
109
		return OC.getRootPath() + '/remote.php/' + service;
110
	},
111
112
	/**
113
	 * @brief Creates an absolute url for remote use
114
	 * @param {string} service id
115
	 * @return {string} the url
116
	 */
117
	linkToRemote: function (service) {
118
		return window.location.protocol + '//' + window.location.host + OC.linkToRemoteBase(service);
119
	},
120
121
	/**
122
	 * Gets the base path for the given OCS API service.
123
	 * @param {string} service name
124
	 * @param {int} version OCS API version
125
	 * @return {string} OCS API base path
126
	 */
127
	linkToOCS: function (service, version) {
128
		version = (version !== 2) ? 1 : 2;
129
		return OC.getProtocol() + '://' + OC.getHost() + OC.getRootPath() + '/ocs/v' + version + '.php/' + service + '/';
130
	},
131
132
	/**
133
	 * Generates the absolute url for the given relative url, which can contain parameters.
134
	 * Parameters will be URL encoded automatically.
135
	 * @param {string} url
136
	 * @param [params] params
137
	 * @param [options] options
138
	 * @param {bool} [options.escape=true] enable/disable auto escape of placeholders (by default enabled)
139
	 * @return {string} Absolute URL for the given relative URL
140
	 */
141
	generateUrl: function (url, params, options) {
142
		var defaultOptions = {
143
				escape: true
144
			},
145
			allOptions = options || {};
146
		_.defaults(allOptions, defaultOptions);
147
148
		var _build = function (text, vars) {
149
			var vars = vars || [];
150
			return text.replace(/{([^{}]*)}/g,
151
				function (a, b) {
152
					var r = (vars[b]);
153
					if (allOptions.escape) {
154
						return (typeof r === 'string' || typeof r === 'number') ? encodeURIComponent(r) : encodeURIComponent(a);
155
					} else {
156
						return (typeof r === 'string' || typeof r === 'number') ? r : a;
157
					}
158
				}
159
			);
160
		};
161
		if (url.charAt(0) !== '/') {
162
			url = '/' + url;
163
164
		}
165
166
		var webRoot = OC.getRootPath();
167
		if (oc_config.modRewriteWorking == true) {
168
			return webRoot + _build(url, params);
169
		}
170
171
		return webRoot + '/index.php' + _build(url, params);
172
	},
173
174
	/**
175
	 * Get the absolute url for a file in an app
176
	 * @param {string} app the id of the app
177
	 * @param {string} type the type of the file to link to (e.g. css,img,ajax.template)
178
	 * @param {string} file the filename
179
	 * @return {string} Absolute URL for a file in an app
180
	 */
181
	filePath: function (app, type, file) {
182
		var isCore = OC.coreApps.indexOf(app) !== -1,
183
			link = OC.getRootPath();
184
		if (file.substring(file.length - 3) === 'php' && !isCore) {
185
			link += '/index.php/apps/' + app;
186
			if (file != 'index.php') {
187
				link += '/';
188
				if (type) {
189
					link += encodeURI(type + '/');
190
				}
191
				link += file;
192
			}
193
		} else if (file.substring(file.length - 3) !== 'php' && !isCore) {
194
			link = OC.appswebroots[app];
195
			if (type) {
196
				link += '/' + type + '/';
197
			}
198
			if (link.substring(link.length - 1) !== '/') {
199
				link += '/';
200
			}
201
			link += file;
202
		} else {
203
			if ((app == 'settings' || app == 'core' || app == 'search') && type == 'ajax') {
204
				link += '/index.php/';
205
			}
206
			else {
207
				link += '/';
208
			}
209
			if (!isCore) {
210
				link += 'apps/';
211
			}
212
			if (app !== '') {
213
				app += '/';
214
				link += app;
215
			}
216
			if (type) {
217
				link += type + '/';
218
			}
219
			link += file;
220
		}
221
		return link;
222
	},
223
224
    /**
225
     * Check if a user file is allowed to be handled.
226
     * @param {string} file to check
227
     */
228
	fileIsBlacklisted: function(file) {
229
		return !!(file.match(oc_config.blacklist_files_regex));
230
	},
231
232
	/**
233
	 * Redirect to the target URL, can also be used for downloads.
234
	 * @param {string} targetURL URL to redirect to
235
	 */
236
	redirect: function (targetURL) {
237
		window.location = targetURL;
238
	},
239
240
	/**
241
	 * Reloads the current page
242
	 */
243
	reload: function () {
244
		window.location.reload();
245
	},
246
247
	/**
248
	 * Protocol that is used to access this ownCloud instance
249
	 * @return {string} Used protocol
250
	 */
251
	getProtocol: function () {
252
		return window.location.protocol.split(':')[0];
253
	},
254
255
	/**
256
	 * Returns the host used to access this ownCloud instance
257
	 * Host is sometimes the same as the hostname but now always.
258
	 *
259
	 * Examples:
260
	 * http://example.com => example.com
261
	 * https://example.com => example.com
262
	 * http://example.com:8080 => example.com:8080
263
	 *
264
	 * @return {string} host
265
	 *
266
	 * @since 8.2
267
	 */
268
	getHost: function () {
269
		return window.location.host;
270
	},
271
272
	/**
273
	 * Returns the hostname used to access this ownCloud instance
274
	 * The hostname is always stripped of the port
275
	 *
276
	 * @return {string} hostname
277
	 * @since 9.0
278
	 */
279
	getHostName: function () {
280
		return window.location.hostname;
281
	},
282
283
	/**
284
	 * Returns the port number used to access this ownCloud instance
285
	 *
286
	 * @return {int} port number
287
	 *
288
	 * @since 8.2
289
	 */
290
	getPort: function () {
291
		return window.location.port;
292
	},
293
294
	/**
295
	 * Returns the web root path where this ownCloud instance
296
	 * is accessible, with a leading slash.
297
	 * For example "/owncloud".
298
	 *
299
	 * @return {string} web root path
300
	 *
301
	 * @since 8.2
302
	 */
303
	getRootPath: function () {
304
		return OC.webroot;
305
	},
306
307
	/**
308
	 * Returns the capabilities
309
	 *
310
	 * @return {array} capabilities
311
	 */
312
	getCapabilities: function() {
313
		return OC._capabilities;
314
	},
315
316
	/**
317
	 * Returns the currently logged in user or null if there is no logged in
318
	 * user (public page mode)
319
	 *
320
	 * @return {OC.CurrentUser} user spec
321
	 * @since 9.0.0
322
	 */
323
	getCurrentUser: function () {
324
		if (!_.isUndefined(window.oc_user)) {
325
			return oc_user;
326
		}
327
		return {
328
			uid: null,
329
			displayName: null,
330
			email: null
331
		};
332
	},
333
334
	/**
335
	 * get the absolute path to an image file
336
	 * if no extension is given for the image, it will automatically decide
337
	 * between .png and .svg based on what the browser supports
338
	 * @param {string} app the app id to which the image belongs
339
	 * @param {string} file the name of the image file
340
	 * @return {string}
341
	 */
342
	imagePath: function (app, file) {
343
		if (file.indexOf('.') == -1) {//if no extension is given, use svg
344
			file += '.svg';
345
		}
346
		return OC.filePath(app, 'img', file);
347
	},
348
349
	/**
350
	 * URI-Encodes a file path but keep the path slashes.
351
	 *
352
	 * @param path path
353
	 * @return encoded path
354
	 */
355
	encodePath: function (path) {
356
		if (!path) {
357
			return path;
358
		}
359
		var parts = path.split('/');
360
		var result = [];
361
		for (var i = 0; i < parts.length; i++) {
362
			result.push(encodeURIComponent(parts[i]));
363
		}
364
		return result.join('/');
365
	},
366
367
	/**
368
	 * Load a script for the server and load it. If the script is already loaded,
369
	 * the event handler will be called directly
370
	 * @param {string} app the app id to which the script belongs
371
	 * @param {string} script the filename of the script
372
	 * @param ready event handler to be called when the script is loaded
373
	 */
374
	addScript: function (app, script, ready) {
375
		var deferred, path = OC.filePath(app, 'js', script + '.js');
376
		if (!OC.addScript.loaded[path]) {
377
			if (ready) {
378
				deferred = $.getScript(path, ready);
379
			} else {
380
				deferred = $.getScript(path);
381
			}
382
			OC.addScript.loaded[path] = deferred;
383
		} else {
384
			if (ready) {
385
				ready();
386
			}
387
		}
388
		return OC.addScript.loaded[path];
389
	},
390
	/**
391
	 * Loads a CSS file
392
	 * @param {string} app the app id to which the css style belongs
393
	 * @param {string} style the filename of the css file
394
	 */
395
	addStyle: function (app, style) {
396
		var path = OC.filePath(app, 'css', style + '.css');
397
		if (OC.addStyle.loaded.indexOf(path) === -1) {
398
			OC.addStyle.loaded.push(path);
399
			if (document.createStyleSheet) {
400
				document.createStyleSheet(path);
401
			} else {
402
				style = $('<link rel="stylesheet" type="text/css" href="' + path + '"/>');
403
				$('head').append(style);
404
			}
405
		}
406
	},
407
408
	/**
409
	 * Loads translations for the given app asynchronously.
410
	 *
411
	 * @param {String} app app name
412
	 * @param {Function} callback callback to call after loading
413
	 * @return {Promise}
414
	 */
415
	addTranslations: function (app, callback) {
416
		return OC.L10N.load(app, callback);
417
	},
418
419
	/**
420
	 * Returns the base name of the given path.
421
	 * For example for "/abc/somefile.txt" it will return "somefile.txt"
422
	 *
423
	 * @param {String} path
424
	 * @return {String} base name
425
	 */
426
	basename: function (path) {
427
		return path.replace(/\\/g, '/').replace(/.*\//, '');
428
	},
429
430
	/**
431
	 * Returns the dir name of the given path.
432
	 * For example for "/abc/somefile.txt" it will return "/abc"
433
	 *
434
	 * @param {String} path
435
	 * @return {String} dir name
436
	 */
437
	dirname: function (path) {
438
		return path.replace(/\\/g, '/').replace(/\/[^\/]*$/, '');
439
	},
440
441
	/**
442
	 * Returns whether the given paths are the same, without
443
	 * leading, trailing or doubled slashes and also removing
444
	 * the dot sections.
445
	 *
446
	 * @param {String} path1 first path
447
	 * @param {String} path2 second path
448
	 * @return {bool} true if the paths are the same
449
	 *
450
	 * @since 9.0
451
	 */
452
	isSamePath: function (path1, path2) {
453
		var filterDot = function (p) {
454
			return p !== '.';
455
		};
456
		var pathSections1 = _.filter((path1 || '').split('/'), filterDot);
457
		var pathSections2 = _.filter((path2 || '').split('/'), filterDot);
458
		path1 = OC.joinPaths.apply(OC, pathSections1);
459
		path2 = OC.joinPaths.apply(OC, pathSections2);
460
		return path1 === path2;
461
	},
462
463
	/**
464
	 * Join path sections
465
	 *
466
	 * @param {...String} path sections
467
	 *
468
	 * @return {String} joined path, any leading or trailing slash
469
	 * will be kept
470
	 *
471
	 * @since 8.2
472
	 */
473
	joinPaths: function () {
474
		if (arguments.length < 1) {
475
			return '';
476
		}
477
		var path = '';
478
		// convert to array
479
		var args = Array.prototype.slice.call(arguments);
480
		// discard empty arguments
481
		args = _.filter(args, function (arg) {
482
			return arg.length > 0;
483
		});
484
		if (args.length < 1) {
485
			return '';
486
		}
487
488
		var lastArg = args[args.length - 1];
489
		var leadingSlash = args[0].charAt(0) === '/';
490
		var trailingSlash = lastArg.charAt(lastArg.length - 1) === '/';
491
		var sections = [];
492
		var i;
493
		for (i = 0; i < args.length; i++) {
494
			sections = sections.concat(args[i].split('/'));
495
		}
496
		var first = !leadingSlash;
497
		for (i = 0; i < sections.length; i++) {
498
			if (sections[i] !== '') {
499
				if (first) {
500
					first = false;
501
				} else {
502
					path += '/';
503
				}
504
				path += sections[i];
505
			}
506
		}
507
508
		if (trailingSlash) {
509
			// add it back
510
			path += '/';
511
		}
512
		return path;
513
	},
514
515
	/**
516
	 * Do a search query and display the results
517
	 * @param {string} query the search query
518
	 */
519
	search: function (query) {
520
		OC.Search.search(query, null, 0, 30);
521
	},
522
	/**
523
	 * Dialog helper for jquery dialogs.
524
	 *
525
	 * @namespace OC.dialogs
526
	 */
527
	dialogs: OCdialogs,
528
	/**
529
	 * Parses a URL query string into a JS map
530
	 * @param {string} queryString query string in the format param1=1234&param2=abcde&param3=xyz
531
	 * @return {Object.<string, string>} map containing key/values matching the URL parameters
532
	 */
533
	parseQueryString: function (queryString) {
534
		var parts,
535
			pos,
536
			components,
537
			result = {},
538
			key,
539
			value;
540
		if (!queryString) {
541
			return null;
542
		}
543
		pos = queryString.indexOf('?');
544
		if (pos >= 0) {
545
			queryString = queryString.substr(pos + 1);
546
		}
547
		parts = queryString.replace(/\+/g, '%20').split('&');
548
		for (var i = 0; i < parts.length; i++) {
549
			// split on first equal sign
550
			var part = parts[i];
551
			pos = part.indexOf('=');
552
			if (pos >= 0) {
553
				components = [
554
					part.substr(0, pos),
555
					part.substr(pos + 1)
556
				];
557
			}
558
			else {
559
				// key only
560
				components = [part];
561
			}
562
			if (!components.length) {
563
				continue;
564
			}
565
			key = decodeURIComponent(components[0]);
566
			if (!key) {
567
				continue;
568
			}
569
			// if equal sign was there, return string
570
			if (components.length > 1) {
571
				result[key] = decodeURIComponent(components[1]);
572
			}
573
			// no equal sign => null value
574
			else {
575
				result[key] = null;
576
			}
577
		}
578
		return result;
579
	},
580
581
	/**
582
	 * Builds a URL query from a JS map.
583
	 * @param {Object.<string, string>} params map containing key/values matching the URL parameters
584
	 * @return {string} String containing a URL query (without question) mark
585
	 */
586
	buildQueryString: function (params) {
587
		if (!params) {
588
			return '';
589
		}
590
		return $.map(params, function (value, key) {
591
			var s = encodeURIComponent(key);
592
			if (value !== null && typeof(value) !== 'undefined') {
593
				s += '=' + encodeURIComponent(value);
594
			}
595
			return s;
596
		}).join('&');
597
	},
598
599
	/**
600
	 * Opens a popup with the setting for an app.
601
	 * @param {string} appid The ID of the app e.g. 'calendar', 'contacts' or 'files'.
602
	 * @param {boolean|string} loadJS If true 'js/settings.js' is loaded. If it's a string
603
	 * it will attempt to load a script by that name in the 'js' directory.
604
	 * @param {boolean} [cache] If true the javascript file won't be forced refreshed. Defaults to true.
605
	 * @param {string} [scriptName] The name of the PHP file to load. Defaults to 'settings.php' in
606
	 * the root of the app directory hierarchy.
607
	 */
608
	appSettings: function (args) {
609
		if (typeof args === 'undefined' || typeof args.appid === 'undefined') {
610
			throw {name: 'MissingParameter', message: 'The parameter appid is missing'};
611
		}
612
		var props = {scriptName: 'settings.php', cache: true};
613
		$.extend(props, args);
614
		var settings = $('#appsettings');
615
		if (settings.length === 0) {
616
			throw {
617
				name: 'MissingDOMElement',
618
				message: 'There has be be an element with id "appsettings" for the popup to show.'
619
			};
620
		}
621
		var popup = $('#appsettings_popup');
622
		if (popup.length === 0) {
623
			$('body').prepend('<div class="popup hidden" id="appsettings_popup"></div>');
624
			popup = $('#appsettings_popup');
625
			popup.addClass(settings.hasClass('topright') ? 'topright' : 'bottomleft');
626
		}
627
		if (popup.is(':visible')) {
628
			popup.hide().remove();
629
		} else {
630
			var arrowclass = settings.hasClass('topright') ? 'up' : 'left';
631
			var jqxhr = $.get(OC.filePath(props.appid, '', props.scriptName), function (data) {
632
				popup.html(data).ready(function () {
633
					popup.prepend('<span class="arrow ' + arrowclass + '"></span><h2>' + t('core', 'Settings') + '</h2><a class="close"></a>').show();
634
					popup.find('.close').bind('click', function () {
635
						popup.remove();
636
					});
637
					if (typeof props.loadJS !== 'undefined') {
638
						var scriptname;
639
						if (props.loadJS === true) {
640
							scriptname = 'settings.js';
641
						} else if (typeof props.loadJS === 'string') {
642
							scriptname = props.loadJS;
643
						} else {
644
							throw {
645
								name: 'InvalidParameter',
646
								message: 'The "loadJS" parameter must be either boolean or a string.'
647
							};
648
						}
649
						if (props.cache) {
650
							$.ajaxSetup({cache: true});
651
						}
652
						$.getScript(OC.filePath(props.appid, 'js', scriptname))
653
							.fail(function (jqxhr, settings, e) {
654
								throw e;
655
							});
656
					}
657
				}).show();
658
			}, 'html');
659
		}
660
	},
661
662
	/**
663
	 * For menu toggling
664
	 * @todo Write documentation
665
	 */
666
	registerMenu: function ($toggle, $menuEl) {
667
		var self = this;
668
		$menuEl.addClass('menu');
669
		$toggle.on('click.menu', function (event) {
670
			// prevent the link event (append anchor to URL)
671
			event.preventDefault();
672
673
			if ($menuEl.is(OC._currentMenu)) {
674
				self.hideMenus();
675
				return;
676
			}
677
			// another menu was open?
678
			else if (OC._currentMenu) {
679
				// close it
680
				self.hideMenus();
681
			}
682
			$menuEl.slideToggle(OC.menuSpeed);
683
			OC._currentMenu = $menuEl;
684
			OC._currentMenuToggle = $toggle;
685
		});
686
	},
687
688
	/**
689
	 *  @todo Write documentation
690
	 */
691
	unregisterMenu: function ($toggle, $menuEl) {
692
		// close menu if opened
693
		if ($menuEl.is(OC._currentMenu)) {
694
			this.hideMenus();
695
		}
696
		$toggle.off('click.menu').removeClass('menutoggle');
697
		$menuEl.removeClass('menu');
698
	},
699
700
	/**
701
	 * Hides any open menus
702
	 *
703
	 * @param {Function} complete callback when the hiding animation is done
704
	 */
705
	hideMenus: function (complete) {
706
		if (OC._currentMenu) {
707
			var lastMenu = OC._currentMenu;
708
			OC._currentMenu.trigger(new $.Event('beforeHide'));
709
			OC._currentMenu.slideUp(OC.menuSpeed, function () {
710
				lastMenu.trigger(new $.Event('afterHide'));
711
				if (complete) {
712
					complete.apply(this, arguments);
713
				}
714
			});
715
		}
716
		OC._currentMenu = null;
717
		OC._currentMenuToggle = null;
718
	},
719
720
	/**
721
	 * Shows a given element as menu
722
	 *
723
	 * @param {Object} [$toggle=null] menu toggle
724
	 * @param {Object} $menuEl menu element
725
	 * @param {Function} complete callback when the showing animation is done
726
	 */
727
	showMenu: function ($toggle, $menuEl, complete) {
728
		if ($menuEl.is(OC._currentMenu)) {
729
			return;
730
		}
731
		this.hideMenus();
732
		OC._currentMenu = $menuEl;
733
		OC._currentMenuToggle = $toggle;
734
		$menuEl.trigger(new $.Event('beforeShow'));
735
		$menuEl.show();
736
		$menuEl.trigger(new $.Event('afterShow'));
737
		// no animation
738
		if (_.isFunction(complete)) {
739
			complete();
740
		}
741
	},
742
743
	/**
744
	 * Wrapper for matchMedia
745
	 *
746
	 * This is makes it possible for unit tests to
747
	 * stub matchMedia (which doesn't work in PhantomJS)
748
	 * @private
749
	 */
750
	_matchMedia: function (media) {
751
		if (window.matchMedia) {
752
			return window.matchMedia(media);
753
		}
754
		return false;
755
	},
756
757
	/**
758
	 * Returns the user's locale
759
	 *
760
	 * @return {String} locale string
761
	 */
762
	getLocale: function () {
763
		return $('html').prop('lang');
764
	},
765
766
	/**
767
	 * Returns whether the current user is an administrator
768
	 *
769
	 * @return {bool} true if the user is an admin, false otherwise
770
	 * @since 9.0.0
771
	 */
772
	isUserAdmin: function () {
773
		return oc_isadmin;
774
	},
775
776
	/**
777
	 * Process ajax error, redirects to main page
778
	 * if an error/auth error status was returned.
779
	 */
780
	_processAjaxError: function (xhr) {
781
		var self = this;
782
		// purposefully aborted request ?
783
		// this._userIsNavigatingAway needed to distinguish ajax calls cancelled by navigating away
784
		// from calls cancelled by failed cross-domain ajax due to SSO redirect
785
		if (xhr.status === 0 && (xhr.statusText === 'abort' || xhr.statusText === 'timeout' || self._reloadCalled)) {
786
			return;
787
		}
788
789
		if (_.contains([0, 302, 303, 307, 401], xhr.status)) {
790
			// sometimes "beforeunload" happens later, so need to defer the reload a bit
791
			setTimeout(function () {
792
				if (!self._userIsNavigatingAway && !self._reloadCalled) {
793
					OC.Notification.show(t('core', 'Problem loading page, reloading in 5 seconds'));
794
					setTimeout(OC.reload, 5000);
795
					// only call reload once
796
					self._reloadCalled = true;
797
				}
798
			}, 100);
799
		}
800
	},
801
802
	/**
803
	 * Registers XmlHttpRequest object for global error processing.
804
	 *
805
	 * This means that if this XHR object returns 401 or session timeout errors,
806
	 * the current page will automatically be reloaded.
807
	 *
808
	 * @param {XMLHttpRequest} xhr
809
	 */
810
	registerXHRForErrorProcessing: function (xhr) {
811
		var loadCallback = function () {
812
			if (xhr.readyState !== 4) {
813
				return;
814
			}
815
816
			if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
817
				return;
818
			}
819
820
			// fire jquery global ajax error handler
821
			$(document).trigger(new $.Event('ajaxError'), xhr);
822
		};
823
824
		var errorCallback = function () {
825
			// fire jquery global ajax error handler
826
			$(document).trigger(new $.Event('ajaxError'), xhr);
827
		};
828
829
		if (xhr.addEventListener) {
830
			xhr.addEventListener('load', loadCallback);
831
			xhr.addEventListener('error', errorCallback);
832
		}
833
834
	}
835
};
836
837
/**
838
 * Current user attributes
839
 *
840
 * @typedef {Object} OC.CurrentUser
841
 *
842
 * @property {String} uid user id
843
 * @property {String} displayName display name
844
 */
845
846
/**
847
 * @namespace OC.Plugins
848
 */
849
OC.Plugins = {
850
	/**
851
	 * @type Array.<OC.Plugin>
852
	 */
853
	_plugins: {},
854
855
	/**
856
	 * Register plugin
857
	 *
858
	 * @param {String} targetName app name / class name to hook into
859
	 * @param {OC.Plugin} plugin
860
	 */
861
	register: function (targetName, plugin) {
862
		var plugins = this._plugins[targetName];
863
		if (!plugins) {
864
			plugins = this._plugins[targetName] = [];
865
		}
866
		plugins.push(plugin);
867
	},
868
869
	/**
870
	 * Returns all plugin registered to the given target
871
	 * name / app name / class name.
872
	 *
873
	 * @param {String} targetName app name / class name to hook into
874
	 * @return {Array.<OC.Plugin>} array of plugins
875
	 */
876
	getPlugins: function (targetName) {
877
		return this._plugins[targetName] || [];
878
	},
879
880
	/**
881
	 * Call attach() on all plugins registered to the given target name.
882
	 *
883
	 * @param {String} targetName app name / class name
884
	 * @param {Object} object to be extended
885
	 * @param {Object} [options] options
886
	 */
887
	attach: function (targetName, targetObject, options) {
888
		var plugins = this.getPlugins(targetName);
889
		for (var i = 0; i < plugins.length; i++) {
890
			if (plugins[i].attach) {
891
				plugins[i].attach(targetObject, options);
892
			}
893
		}
894
	},
895
896
	/**
897
	 * Call detach() on all plugins registered to the given target name.
898
	 *
899
	 * @param {String} targetName app name / class name
900
	 * @param {Object} object to be extended
901
	 * @param {Object} [options] options
902
	 */
903
	detach: function (targetName, targetObject, options) {
904
		var plugins = this.getPlugins(targetName);
905
		for (var i = 0; i < plugins.length; i++) {
906
			if (plugins[i].detach) {
907
				plugins[i].detach(targetObject, options);
908
			}
909
		}
910
	},
911
912
	/**
913
	 * Plugin
914
	 *
915
	 * @todo make this a real class in the future
916
	 * @typedef {Object} OC.Plugin
917
	 *
918
	 * @property {String} name plugin name
919
	 * @property {Function} attach function that will be called when the
920
	 * plugin is attached
921
	 * @property {Function} [detach] function that will be called when the
922
	 * plugin is detached
923
	 */
924
925
};
926
927
/**
928
 * @namespace OC.search
929
 */
930
OC.search.customResults = {};
931
/**
932
 * @deprecated use get/setFormatter() instead
933
 */
934
OC.search.resultTypes = {};
935
936
OC.addStyle.loaded = [];
937
OC.addScript.loaded = [];
938
939
/**
940
 * A little class to manage a status field for a "saving" process.
941
 * It can be used to display a starting message (e.g. "Saving...") and then
942
 * replace it with a green success message or a red error message.
943
 *
944
 * @namespace OC.msg
945
 */
946
OC.msg = {
947
	/**
948
	 * Displayes a "Saving..." message in the given message placeholder
949
	 *
950
	 * @param {Object} selector    Placeholder to display the message in
951
	 */
952
	startSaving: function (selector) {
953
		this.startAction(selector, t('core', 'Saving...'));
954
	},
955
956
	/**
957
	 * Displayes a custom message in the given message placeholder
958
	 *
959
	 * @param {Object} selector    Placeholder to display the message in
960
	 * @param {string} message    Plain text message to display (no HTML allowed)
961
	 */
962
	startAction: function (selector, message) {
963
		$(selector).text(message)
964
			.removeClass('success')
965
			.removeClass('error')
966
			.stop(true, true)
967
			.show();
968
	},
969
970
	/**
971
	 * Displayes an success/error message in the given selector
972
	 *
973
	 * @param {Object} selector    Placeholder to display the message in
974
	 * @param {Object} response    Response of the server
975
	 * @param {Object} response.data    Data of the servers response
976
	 * @param {string} response.data.message    Plain text message to display (no HTML allowed)
977
	 * @param {string} response.status    is being used to decide whether the message
978
	 * is displayed as an error/success
979
	 */
980
	finishedSaving: function (selector, response) {
981
		this.finishedAction(selector, response);
982
	},
983
984
	/**
985
	 * Displayes an success/error message in the given selector
986
	 *
987
	 * @param {Object} selector    Placeholder to display the message in
988
	 * @param {Object} response    Response of the server
989
	 * @param {Object} response.data Data of the servers response
990
	 * @param {string} response.data.message Plain text message to display (no HTML allowed)
991
	 * @param {string} response.status is being used to decide whether the message
992
	 * is displayed as an error/success
993
	 */
994
	finishedAction: function (selector, response) {
995
		if (response.status === "success" || response.status === "ok") {
996
			this.finishedSuccess(selector, response.data.message);
997
		} else {
998
			this.finishedError(selector, response.data.message);
999
		}
1000
	},
1001
1002
	/**
1003
	 * Displayes an success message in the given selector
1004
	 *
1005
	 * @param {Object} selector Placeholder to display the message in
1006
	 * @param {string} message Plain text success message to display (no HTML allowed)
1007
	 */
1008
	finishedSuccess: function (selector, message) {
1009
		$(selector).text(message)
1010
			.addClass('success')
1011
			.removeClass('error')
1012
			.stop(true, true)
1013
			.delay(3000)
1014
			.fadeOut(900)
1015
			.show();
1016
	},
1017
1018
	/**
1019
	 * Displayes an error message in the given selector
1020
	 *
1021
	 * @param {Object} selector Placeholder to display the message in
1022
	 * @param {string} message Plain text error message to display (no HTML allowed)
1023
	 */
1024
	finishedError: function (selector, message) {
1025
		$(selector).text(message)
1026
			.addClass('error')
1027
			.removeClass('success')
1028
			.show();
1029
	}
1030
};
1031
1032
/**
1033
 * @todo Write documentation
1034
 * @namespace
1035
 */
1036
OC.Notification = {
1037
	queuedNotifications: [],
1038
	getDefaultNotificationFunction: null,
1039
1040
	/**
1041
	 * @type Array.<int> array of notification timers
1042
	 */
1043
	notificationTimers: [],
1044
1045
	/**
1046
	 * @param callback
1047
	 * @todo Write documentation
1048
	 */
1049
	setDefault: function (callback) {
1050
		OC.Notification.getDefaultNotificationFunction = callback;
1051
	},
1052
1053
	/**
1054
	 * Hides a notification.
1055
	 *
1056
	 * If a row is given, only hide that one.
1057
	 * If no row is given, hide all notifications.
1058
	 *
1059
	 * @param {jQuery} [$row] notification row
1060
	 * @param {Function} [callback] callback
1061
	 */
1062
	hide: function ($row, callback) {
1063
		var self = this;
1064
		var $notification = $('#notification');
1065
1066
		if (_.isFunction($row)) {
1067
			// first arg is the callback
1068
			callback = $row;
1069
			$row = undefined;
1070
		}
1071
1072
		if (!$row) {
1073
			console.warn('Missing argument $row in OC.Notification.hide() call, caller needs to be adjusted to only dismiss its own notification');
1074
			// assume that the row to be hidden is the first one
1075
			$row = $notification.find('.row:first');
1076
		}
1077
1078
		if ($row && $notification.find('.row').length > 1) {
1079
			// remove the row directly
1080
			$row.remove();
1081
			if (callback) {
1082
				callback.call();
1083
			}
1084
			return;
1085
		}
1086
1087
		_.defer(function () {
1088
			// fade out is supposed to only fade when there is a single row
1089
			// however, some code might call hide() and show() directly after,
1090
			// which results in more than one element
1091
			// in this case, simply delete that one element that was supposed to
1092
			// fade out
1093
			//
1094
			// FIXME: remove once all callers are adjusted to only hide their own notifications
1095
			if ($notification.find('.row').length > 1) {
1096
				$row.remove();
1097
				return;
1098
			}
1099
1100
			// else, fade out whatever was present
1101
			$notification.fadeOut('400', function () {
1102
				if (self.isHidden()) {
1103
					if (self.getDefaultNotificationFunction) {
1104
						self.getDefaultNotificationFunction.call();
1105
					}
1106
				}
1107
				if (callback) {
1108
					callback.call();
1109
				}
1110
				$notification.empty();
1111
			});
1112
		});
1113
	},
1114
1115
	/**
1116
	 * Shows a notification as HTML without being sanitized before.
1117
	 * If you pass unsanitized user input this may lead to a XSS vulnerability.
1118
	 * Consider using show() instead of showHTML()
1119
	 *
1120
	 * @param {string} html Message to display
1121
	 * @param {Object} [options] options
1122
	 * @param {string] [options.type] notification type
1123
	 * @param {int} [options.timeout=0] timeout value, defaults to 0 (permanent)
1124
	 * @return {jQuery} jQuery element for notification row
1125
	 */
1126
	showHtml: function (html, options) {
1127
		options = options || {};
1128
		_.defaults(options, {
1129
			timeout: 0
1130
		});
1131
1132
		var self = this;
1133
		var $notification = $('#notification');
1134
		if (this.isHidden()) {
1135
			$notification.fadeIn().css('display', 'inline-block');
1136
		}
1137
		var $row = $('<div class="row"></div>');
1138
		if (options.type) {
1139
			$row.addClass('type-' + options.type);
1140
		}
1141
		if (options.type === 'error') {
1142
			// add a close button
1143
			var $closeButton = $('<a class="action close icon-close" href="#"></a>');
1144
			$closeButton.attr('alt', t('core', 'Dismiss'));
1145
			$row.append($closeButton);
1146
			$closeButton.one('click', function () {
1147
				self.hide($row);
1148
				return false;
1149
			});
1150
			$row.addClass('closeable');
1151
		}
1152
1153
		$row.prepend(html);
1154
		$notification.append($row);
1155
1156
		if (options.timeout > 0) {
1157
			// register timeout to vanish notification
1158
			this.notificationTimers.push(setTimeout(function () {
1159
				self.hide($row);
1160
			}, (options.timeout * 1000)));
1161
		}
1162
1163
		return $row;
1164
	},
1165
1166
	/**
1167
	 * Shows a sanitized notification
1168
	 *
1169
	 * @param {string} text Message to display
1170
	 * @param {Object} [options] options
1171
	 * @param {string] [options.type] notification type
1172
	 * @param {int} [options.timeout=0] timeout value, defaults to 0 (permanent)
1173
	 * @return {jQuery} jQuery element for notification row
1174
	 */
1175
	show: function (text, options) {
1176
		return this.showHtml($('<div/>').text(text).html(), options);
1177
	},
1178
1179
	/**
1180
	 * Shows a notification that disappears after x seconds, default is
1181
	 * 7 seconds
1182
	 *
1183
	 * @param {string} text Message to show
1184
	 * @param {array} [options] options array
1185
	 * @param {int} [options.timeout=7] timeout in seconds, if this is 0 it will show the message permanently
1186
	 * @param {boolean} [options.isHTML=false] an indicator for HTML notifications (true) or text (false)
1187
	 * @param {string] [options.type] notification type
1188
	 */
1189
	showTemporary: function (text, options) {
1190
		var self = this;
1191
		var defaults = {
1192
			isHTML: false,
1193
			timeout: 7
1194
		};
1195
		options = options || {};
1196
		// merge defaults with passed in options
1197
		_.defaults(options, defaults);
1198
1199
		var $row;
1200
		if (options.isHTML) {
1201
			$row = this.showHtml(text, options);
1202
		} else {
1203
			$row = this.show(text, options);
1204
		}
1205
		return $row;
1206
	},
1207
1208
	/**
1209
	 * Returns whether a notification is hidden.
1210
	 * @return {boolean}
1211
	 */
1212
	isHidden: function () {
1213
		return !$("#notification").find('.row').length;
1214
	}
1215
};
1216
1217
/**
1218
 * Breadcrumb class
1219
 *
1220
 * @namespace
1221
 *
1222
 * @deprecated will be replaced by the breadcrumb implementation
1223
 * of the files app in the future
1224
 */
1225
OC.Breadcrumb = {
1226
	container: null,
1227
	/**
1228
	 * @todo Write documentation
1229
	 * @param dir
1230
	 * @param leafName
1231
	 * @param leafLink
1232
	 */
1233
	show: function (dir, leafName, leafLink) {
1234
		if (!this.container) {//default
1235
			this.container = $('#controls');
1236
		}
1237
		this._show(this.container, dir, leafName, leafLink);
1238
	},
1239
	_show: function (container, dir, leafname, leaflink) {
1240
		var self = this;
1241
1242
		this._clear(container);
1243
1244
		// show home + path in subdirectories
1245
		if (dir) {
1246
			//add home
1247
			var link = OC.linkTo('files', 'index.php');
1248
1249
			var crumb = $('<div/>');
1250
			crumb.addClass('crumb');
1251
1252
			var crumbLink = $('<a/>');
1253
			crumbLink.attr('href', link);
1254
1255
			var crumbImg = $('<img/>');
1256
			crumbImg.attr('src', OC.imagePath('core', 'places/home'));
1257
			crumbLink.append(crumbImg);
1258
			crumb.append(crumbLink);
1259
			container.prepend(crumb);
1260
1261
			//add path parts
1262
			var segments = dir.split('/');
1263
			var pathurl = '';
1264
			jQuery.each(segments, function (i, name) {
1265
				if (name !== '') {
1266
					pathurl = pathurl + '/' + name;
1267
					var link = OC.linkTo('files', 'index.php') + '?dir=' + encodeURIComponent(pathurl);
1268
					self._push(container, name, link);
1269
				}
1270
			});
1271
		}
1272
1273
		//add leafname
1274
		if (leafname && leaflink) {
1275
			this._push(container, leafname, leaflink);
1276
		}
1277
	},
1278
1279
	/**
1280
	 * @todo Write documentation
1281
	 * @param {string} name
1282
	 * @param {string} link
1283
	 */
1284
	push: function (name, link) {
1285
		if (!this.container) {//default
1286
			this.container = $('#controls');
1287
		}
1288
		return this._push(OC.Breadcrumb.container, name, link);
1289
	},
1290
	_push: function (container, name, link) {
1291
		var crumb = $('<div/>');
1292
		crumb.addClass('crumb').addClass('last');
1293
1294
		var crumbLink = $('<a/>');
1295
		crumbLink.attr('href', link);
1296
		crumbLink.text(name);
1297
		crumb.append(crumbLink);
1298
1299
		var existing = container.find('div.crumb');
1300
		if (existing.length) {
1301
			existing.removeClass('last');
1302
			existing.last().after(crumb);
1303
		} else {
1304
			container.prepend(crumb);
1305
		}
1306
		return crumb;
1307
	},
1308
1309
	/**
1310
	 * @todo Write documentation
1311
	 */
1312
	pop: function () {
1313
		if (!this.container) {//default
1314
			this.container = $('#controls');
1315
		}
1316
		this.container.find('div.crumb').last().remove();
1317
		this.container.find('div.crumb').last().addClass('last');
1318
	},
1319
1320
	/**
1321
	 * @todo Write documentation
1322
	 */
1323
	clear: function () {
1324
		if (!this.container) {//default
1325
			this.container = $('#controls');
1326
		}
1327
		this._clear(this.container);
1328
	},
1329
	_clear: function (container) {
1330
		container.find('div.crumb').remove();
1331
	}
1332
};
1333
1334
if (typeof localStorage !== 'undefined' && localStorage !== null) {
1335
	/**
1336
	 * User and instance aware localstorage
1337
	 * @namespace
1338
	 */
1339
	OC.localStorage = {
1340
		namespace: 'oc_' + OC.currentUser + '_' + OC.webroot + '_',
1341
1342
		/**
1343
		 * Whether the storage contains items
1344
		 * @param {string} name
1345
		 * @return {boolean}
1346
		 */
1347
		hasItem: function (name) {
1348
			return OC.localStorage.getItem(name) !== null;
1349
		},
1350
1351
		/**
1352
		 * Add an item to the storage
1353
		 * @param {string} name
1354
		 * @param {string} item
1355
		 */
1356
		setItem: function (name, item) {
1357
			return localStorage.setItem(OC.localStorage.namespace + name, JSON.stringify(item));
1358
		},
1359
1360
		/**
1361
		 * Removes an item from the storage
1362
		 * @param {string} name
1363
		 * @param {string} item
1364
		 */
1365
		removeItem: function (name, item) {
1366
			return localStorage.removeItem(OC.localStorage.namespace + name);
1367
		},
1368
1369
		/**
1370
		 * Get an item from the storage
1371
		 * @param {string} name
1372
		 * @return {null|string}
1373
		 */
1374
		getItem: function (name) {
1375
			var item = localStorage.getItem(OC.localStorage.namespace + name);
1376
			if (item === null) {
1377
				return null;
1378
			} else {
1379
				return JSON.parse(item);
1380
			}
1381
		}
1382
	};
1383
} else {
1384
	//dummy localstorage
1385
	OC.localStorage = {
1386
		hasItem: function () {
1387
			return false;
1388
		},
1389
		setItem: function () {
1390
			return false;
1391
		},
1392
		getItem: function () {
1393
			return null;
1394
		}
1395
	};
1396
}
1397
1398
/**
1399
 * prototypical inheritance functions
1400
 * @todo Write documentation
1401
 * usage:
1402
 * MySubObject=object(MyObject)
1403
 */
1404
function object(o) {
1405
	function F() {
1406
	}
1407
1408
	F.prototype = o;
1409
	return new F();
1410
}
1411
1412
/**
1413
 * Initializes core
1414
 */
1415
function initCore() {
1416
	/**
1417
	 * Disable automatic evaluation of responses for $.ajax() functions (and its
1418
	 * higher-level alternatives like $.get() and $.post()).
1419
	 *
1420
	 * If a response to a $.ajax() request returns a content type of "application/javascript"
1421
	 * JQuery would previously execute the response body. This is a pretty unexpected
1422
	 * behaviour and can result in a bypass of our Content-Security-Policy as well as
1423
	 * multiple unexpected XSS vectors.
1424
	 */
1425
	$.ajaxSetup({
1426
		contents: {
1427
			script: false
1428
		}
1429
	});
1430
1431
	/**
1432
	 * Set users locale to moment.js as soon as possible
1433
	 */
1434
	moment.locale(OC.getLocale());
1435
1436
	var userAgent = window.navigator.userAgent;
1437
	var msie = userAgent.indexOf('MSIE ');
1438
	var trident = userAgent.indexOf('Trident/');
1439
	var edge = userAgent.indexOf('Edge/');
1440
1441
	if (msie > 0 || trident > 0) {
1442
		// (IE 10 or older) || IE 11
1443
		$('html').addClass('ie');
1444
	} else if (edge > 0) {
1445
		// for edge
1446
		$('html').addClass('edge');
1447
	}
1448
1449
	$(window).on('unload.main', function () {
1450
		OC._unloadCalled = true;
1451
	});
1452
	$(window).on('beforeunload.main', function () {
1453
		// super-trick thanks to http://stackoverflow.com/a/4651049
1454
		// in case another handler displays a confirmation dialog (ex: navigating away
1455
		// during an upload), there are two possible outcomes: user clicked "ok" or
1456
		// "cancel"
1457
1458
		// first timeout handler is called after unload dialog is closed
1459
		setTimeout(function () {
1460
			OC._userIsNavigatingAway = true;
1461
1462
			// second timeout event is only called if user cancelled (Chrome),
1463
			// but in other browsers it might still be triggered, so need to
1464
			// set a higher delay...
1465
			setTimeout(function () {
1466
				if (!OC._unloadCalled) {
1467
					OC._userIsNavigatingAway = false;
1468
				}
1469
			}, 10000);
1470
		}, 1);
1471
	});
1472
	$(document).on('ajaxError.main', function (event, request, settings) {
1473
		if (settings && settings.allowAuthErrors) {
1474
			return;
1475
		}
1476
		OC._processAjaxError(request);
1477
	});
1478
1479
	/**
1480
	 * Calls the server periodically to ensure that session doesn't
1481
	 * time out
1482
	 */
1483
	function initSessionHeartBeat() {
1484
		// max interval in seconds set to 24 hours
1485
		var maxInterval = 24 * 3600;
1486
		// interval in seconds
1487
		var interval = 900;
1488
		if (oc_config.session_lifetime) {
1489
			interval = Math.floor(oc_config.session_lifetime / 2);
1490
		}
1491
		// minimum one minute
1492
		if (interval < 60) {
1493
			interval = 60;
1494
		}
1495
		if (interval > maxInterval) {
1496
			interval = maxInterval;
1497
		}
1498
		var url = OC.generateUrl('/heartbeat');
1499
		var heartBeatTimeout = null;
1500
		var heartBeat = function () {
1501
			clearInterval(heartBeatTimeout);
1502
			heartBeatTimeout = setInterval(function () {
1503
				$.post(url);
1504
			}, interval * 1000);
1505
		};
1506
		$(document).ajaxComplete(heartBeat);
1507
		heartBeat();
1508
	}
1509
1510
	// session heartbeat (defaults to enabled)
1511
	if (typeof(oc_config.session_keepalive) === 'undefined' || !!oc_config.session_keepalive) {
1512
1513
		initSessionHeartBeat();
1514
	}
1515
1516
	OC.registerMenu($('#expand'), $('#expanddiv'));
1517
1518
	// toggle for menus
1519
	$(document).on('mouseup.closemenus', function (event) {
1520
		var $el = $(event.target);
1521
		if ($el.closest('.menu').length || $el.closest('.menutoggle').length) {
1522
			// don't close when clicking on the menu directly or a menu toggle
1523
			return false;
1524
		}
1525
1526
		OC.hideMenus();
1527
	});
1528
1529
	/**
1530
	 * Set up the main menu toggle to react to media query changes.
1531
	 * If the screen is small enough, the main menu becomes a toggle.
1532
	 * If the screen is bigger, the main menu is not a toggle any more.
1533
	 */
1534
	function setupMainMenu() {
1535
		// toggle the navigation
1536
		var $toggle = $('#header .header-appname-container');
1537
		var $navigation = $('#navigation');
1538
1539
		// init the menu
1540
		OC.registerMenu($toggle, $navigation);
1541
		$toggle.data('oldhref', $toggle.attr('href'));
1542
		$toggle.attr('href', '#');
1543
		$navigation.hide();
1544
1545
		// show loading feedback
1546
		$navigation.delegate('a', 'click', function (event) {
1547
			var $app = $(event.target);
1548
			if (!$app.is('a')) {
1549
				$app = $app.closest('a');
1550
			}
1551
			if (!event.ctrlKey) {
1552
				$app.addClass('app-loading');
1553
			} else {
1554
				// Close navigation when opening app in
1555
				// a new tab
1556
				OC.hideMenus();
1557
			}
1558
		});
1559
	}
1560
1561
	function setupUserMenu() {
1562
		var $menu = $('#header #settings');
1563
1564
		$menu.delegate('a', 'click', function (event) {
1565
			var $page = $(event.target);
1566
			if (!$page.is('a')) {
1567
				$page = $page.closest('a');
1568
			}
1569
			$page.find('img').remove();
1570
			$page.find('div').remove(); // prevent odd double-clicks
1571
			$page.prepend($('<div/>').addClass('icon-loading-small-dark'));
1572
		});
1573
	}
1574
1575
	setupMainMenu();
1576
	setupUserMenu();
1577
1578
	// just add snapper for logged in users
1579
	if ($('#app-navigation').length && !$('html').hasClass('lte9')) {
1580
1581
		// App sidebar on mobile
1582
		var snapper = new Snap({
1583
			element: document.getElementById('app-content'),
1584
			disable: 'right',
1585
			maxPosition: 250,
1586
			minDragDistance: 100
1587
		});
1588
		$('#app-content').prepend('<div id="app-navigation-toggle" class="icon-menu" style="display:none;"></div>');
1589
		$('#app-navigation-toggle').click(function () {
1590
			if (snapper.state().state == 'left') {
1591
				snapper.close();
1592
			} else {
1593
				snapper.open('left');
1594
			}
1595
		});
1596
		// close sidebar when switching navigation entry
1597
		var $appNavigation = $('#app-navigation');
1598
		$appNavigation.delegate('a, :button', 'click', function (event) {
1599
			var $target = $(event.target);
1600
			// don't hide navigation when changing settings or adding things
1601
			if ($target.is('.app-navigation-noclose') ||
1602
				$target.closest('.app-navigation-noclose').length) {
1603
				return;
1604
			}
1605
			if ($target.is('.add-new') ||
1606
				$target.closest('.add-new').length) {
1607
				return;
1608
			}
1609
			if ($target.is('#app-settings') ||
1610
				$target.closest('#app-settings').length) {
1611
				return;
1612
			}
1613
			snapper.close();
1614
		});
1615
1616
		var toggleSnapperOnSize = function () {
1617
			if ($(window).width() > 768) {
1618
				snapper.close();
1619
				snapper.disable();
1620
			} else {
1621
				snapper.enable();
1622
			}
1623
		};
1624
1625
		$(window).resize(_.debounce(toggleSnapperOnSize, 250));
1626
1627
		// initial call
1628
		toggleSnapperOnSize();
1629
1630
		// adjust controls bar width
1631
		var adjustControlsWidth = function () {
1632
			if ($('#controls').length) {
1633
				var controlsWidth;
1634
				// if there is a scrollbar …
1635
				if ($('#app-content').get(0).scrollHeight > $('#app-content').height()) {
1636
					if ($(window).width() > 768) {
1637
						controlsWidth = $('#content').width() - $('#app-navigation').width() - getScrollBarWidth();
1638
						if (!$('#app-sidebar').hasClass('hidden') && !$('#app-sidebar').hasClass('disappear')) {
1639
							controlsWidth -= $('#app-sidebar').width();
1640
						}
1641
					} else {
1642
						controlsWidth = $('#content').width() - getScrollBarWidth();
1643
					}
1644
				} else { // if there is none
1645
					if ($(window).width() > 768) {
1646
						controlsWidth = $('#content').width() - $('#app-navigation').width();
1647
						if (!$('#app-sidebar').hasClass('hidden') && !$('#app-sidebar').hasClass('disappear')) {
1648
							controlsWidth -= $('#app-sidebar').width();
1649
						}
1650
					} else {
1651
						controlsWidth = $('#content').width();
1652
					}
1653
				}
1654
				$('#controls').css('width', controlsWidth);
1655
				$('#controls').css('min-width', controlsWidth);
1656
			}
1657
		};
1658
1659
		$(window).resize(_.debounce(adjustControlsWidth, 250));
1660
1661
		$('body').delegate('#app-content', 'apprendered appresized', adjustControlsWidth);
1662
1663
	}
1664
}
1665
1666
/**
1667
 * Disable the use of globalEval in jQuery 2.1.4.
1668
 * This is required for API compatibility, yet should not be available all the
1669
 * same.
1670
 *
1671
 * @see https://github.com/jquery/jquery/issues/2432 for further details.
1672
 */
1673
$.fn.globalEval = function(){};
1674
1675
$(document).ready(initCore);
1676
1677
/**
1678
 * Filter Jquery selector by attribute value
1679
 */
1680
$.fn.filterAttr = function (attr_name, attr_value) {
1681
	return this.filter(function () {
1682
		return $(this).attr(attr_name) === attr_value;
1683
	});
1684
};
1685
1686
/**
1687
 * Returns a human readable file size
1688
 * @param {number} size Size in bytes
1689
 * @param {boolean} skipSmallSizes return '< 1 kB' for small files
1690
 * @return {string}
1691
 */
1692
function humanFileSize(size, skipSmallSizes) {
1693
	var humanList = ['B', 'KB', 'MB', 'GB', 'TB'];
1694
	// Calculate Log with base 1024: size = 1024 ** order
1695
	var order = size > 0 ? Math.floor(Math.log(size) / Math.log(1024)) : 0;
1696
	// Stay in range of the byte sizes that are defined
1697
	order = Math.min(humanList.length - 1, order);
1698
	var readableFormat = humanList[order];
1699
	var relativeSize = (size / Math.pow(1024, order)).toFixed(1);
1700
	if (skipSmallSizes === true && order === 0) {
1701
		if (relativeSize !== "0.0") {
1702
			return '< 1 KB';
1703
		} else {
1704
			return '0 KB';
1705
		}
1706
	}
1707
	if (order < 2) {
1708
		relativeSize = parseFloat(relativeSize).toFixed(0);
1709
	}
1710
	else if (relativeSize.substr(relativeSize.length - 2, 2) === '.0') {
1711
		relativeSize = relativeSize.substr(0, relativeSize.length - 2);
1712
	}
1713
	return relativeSize + ' ' + readableFormat;
1714
}
1715
1716
/**
1717
 * Format an UNIX timestamp to a human understandable format
1718
 * @param {number} timestamp UNIX timestamp
1719
 * @return {string} Human readable format
1720
 */
1721
function formatDate(timestamp) {
1722
	return OC.Util.formatDate(timestamp);
1723
}
1724
1725
//
1726
/**
1727
 * Get the value of a URL parameter
1728
 * @link http://stackoverflow.com/questions/1403888/get-url-parameter-with-jquery
1729
 * @param {string} name URL parameter
1730
 * @return {string}
1731
 */
1732
function getURLParameter(name) {
1733
	return decodeURI(
1734
		(RegExp(name + '=' + '(.+?)(&|$)').exec(location.search) || [, null])[1]
1735
	);
1736
}
1737
1738
/**
1739
 * Takes an absolute timestamp and return a string with a human-friendly relative date
1740
 * @param {number} timestamp A Unix timestamp
1741
 */
1742
function relative_modified_date(timestamp) {
1743
	/*
1744
	 Were multiplying by 1000 to bring the timestamp back to its original value
1745
	 per https://github.com/owncloud/core/pull/10647#discussion_r16790315
1746
	 */
1747
	return OC.Util.relativeModifiedDate(timestamp * 1000);
1748
}
1749
1750
/**
1751
 * Utility functions
1752
 * @namespace
1753
 */
1754
OC.Util = {
1755
	// TODO: remove original functions from global namespace
1756
	humanFileSize: humanFileSize,
1757
	
1758
	/**
1759
	* regular expression to parse size in bytes from a humanly readable string
1760
	* see computerFileSize(string)
1761
	*/
1762
	_computerFileSizeRegexp: /^([0-9]*)(\.([0-9]+))?( +)?([kmgtp]?b?)$/i,
1763
1764
	/**
1765
	 * Returns a file size in bytes from a humanly readable string
1766
	 * Makes 2kB to 2048.
1767
	 * Inspired by computerFileSize in helper.php
1768
	 * @param  {string} string file size in human readable format
1769
	 * @return {number} or null if string could not be parsed
1770
	 *
1771
	 *
1772
	 */
1773
	computerFileSize: function (string) {
1774
		if (typeof string !== 'string') {
1775
			return null;
1776
		}
1777
1778
		var s = string.toLowerCase().trim();
1779
		var bytes = null;
1780
		
1781
		var bytesArray = {
1782
			'b': 1,
1783
			'k': 1024,
1784
			'kb': 1024,
1785
			'mb': 1024 * 1024,
1786
			'm': 1024 * 1024,
1787
			'gb': 1024 * 1024 * 1024,
1788
			'g': 1024 * 1024 * 1024,
1789
			'tb': 1024 * 1024 * 1024 * 1024,
1790
			't': 1024 * 1024 * 1024 * 1024,
1791
			'pb': 1024 * 1024 * 1024 * 1024 * 1024,
1792
			'p': 1024 * 1024 * 1024 * 1024 * 1024
1793
		};
1794
1795
		var matches = s.match(this._computerFileSizeRegexp);
1796
		if (matches !== null) {
1797
			bytes = parseFloat(s);
1798
			if (!isFinite(bytes)) {
1799
				return null;
1800
			}
1801
		} else {
1802
			return null;
1803
		}
1804
		if (matches[5]) {
1805
			bytes = bytes * bytesArray[matches[5]];
1806
		}
1807
1808
		bytes = Math.round(bytes);
1809
		return bytes;
1810
	},
1811
1812
	/**
1813
	 * @param timestamp
1814
	 * @param format
1815
	 * @returns {string} timestamp formatted as requested
1816
	 */
1817
	formatDate: function (timestamp, format) {
1818
		format = format || "LLL";
1819
		return moment(timestamp).format(format);
1820
	},
1821
1822
	/**
1823
	 * @param timestamp
1824
	 * @returns {string} human readable difference from now
1825
	 */
1826
	relativeModifiedDate: function (timestamp) {
1827
		var diff = moment().diff(moment(timestamp));
1828
		if (diff >= 0 && diff < 45000) {
1829
			return t('core', 'seconds ago');
1830
		}
1831
		return moment(timestamp).fromNow();
1832
	},
1833
	/**
1834
	 * Returns whether the browser supports SVG
1835
	 * @deprecated SVG is always supported (since 9.0)
1836
	 * @return {boolean} true if the browser supports SVG, false otherwise
1837
	 */
1838
	hasSVGSupport: function () {
1839
		return true
1840
	},
1841
	/**
1842
	 * If SVG is not supported, replaces the given icon's extension
1843
	 * from ".svg" to ".png".
1844
	 * If SVG is supported, return the image path as is.
1845
	 * @param {string} file image path with svg extension
1846
	 * @deprecated SVG is always supported (since 9.0)
1847
	 * @return {string} fixed image path with png extension if SVG is not supported
1848
	 */
1849
	replaceSVGIcon: function (file) {
1850
		return file;
1851
	},
1852
	/**
1853
	 * Replace SVG images in all elements that have the "svg" class set
1854
	 * with PNG images.
1855
	 *
1856
	 * @param $el root element from which to search, defaults to $('body')
1857
	 * @deprecated SVG is always supported (since 9.0)
1858
	 */
1859
	replaceSVG: function ($el) {
1860
	},
1861
1862
	/**
1863
	 * Fix image scaling for IE8, since background-size is not supported.
1864
	 *
1865
	 * This scales the image to the element's actual size, the URL is
1866
	 * taken from the "background-image" CSS attribute.
1867
	 *
1868
	 * @deprecated IE8 isn't supported since 9.0
1869
	 * @param {Object} $el image element
1870
	 */
1871
	scaleFixForIE8: function ($el) {
1872
	},
1873
1874
	/**
1875
	 * Returns whether this is IE
1876
	 *
1877
	 * @deprecated Use bowser.mise instead (since 9.1)
1878
	 * @return {bool} true if this is IE, false otherwise
1879
	 */
1880
	isIE: function () {
1881
		return bowser.msie;
1882
	},
1883
1884
	/**
1885
	 * Returns whether this is IE8
1886
	 *
1887
	 * @deprecated IE8 isn't supported since 9.0
1888
	 * @return {bool} false (IE8 isn't supported anymore)
1889
	 */
1890
	isIE8: function () {
1891
		return false;
1892
	},
1893
1894
	/**
1895
	 * Returns the width of a generic browser scrollbar
1896
	 *
1897
	 * @return {int} width of scrollbar
1898
	 */
1899
	getScrollBarWidth: function () {
1900
		if (this._scrollBarWidth) {
1901
			return this._scrollBarWidth;
1902
		}
1903
1904
		var inner = document.createElement('p');
1905
		inner.style.width = "100%";
1906
		inner.style.height = "200px";
1907
1908
		var outer = document.createElement('div');
1909
		outer.style.position = "absolute";
1910
		outer.style.top = "0px";
1911
		outer.style.left = "0px";
1912
		outer.style.visibility = "hidden";
1913
		outer.style.width = "200px";
1914
		outer.style.height = "150px";
1915
		outer.style.overflow = "hidden";
1916
		outer.appendChild(inner);
1917
1918
		document.body.appendChild(outer);
1919
		var w1 = inner.offsetWidth;
1920
		outer.style.overflow = 'scroll';
1921
		var w2 = inner.offsetWidth;
1922
		if (w1 === w2) {
1923
			w2 = outer.clientWidth;
1924
		}
1925
1926
		document.body.removeChild(outer);
1927
1928
		this._scrollBarWidth = (w1 - w2);
1929
1930
		return this._scrollBarWidth;
1931
	},
1932
1933
	/**
1934
	 * Remove the time component from a given date
1935
	 *
1936
	 * @param {Date} date date
1937
	 * @return {Date} date with stripped time
1938
	 */
1939
	stripTime: function (date) {
1940
		// FIXME: likely to break when crossing DST
1941
		// would be better to use a library like momentJS
1942
		return new Date(date.getFullYear(), date.getMonth(), date.getDate());
1943
	},
1944
1945
	_chunkify: function (t) {
1946
		// Adapted from http://my.opera.com/GreyWyvern/blog/show.dml/1671288
1947
		var tz = [], x = 0, y = -1, n = 0, code, c;
1948
1949
		while (x < t.length) {
1950
			c = t.charAt(x);
1951
			// only include the dot in strings
1952
			var m = ((!n && c === '.') || (c >= '0' && c <= '9'));
1953
			if (m !== n) {
1954
				// next chunk
1955
				y++;
1956
				tz[y] = '';
1957
				n = m;
1958
			}
1959
			tz[y] += c;
1960
			x++;
1961
		}
1962
		return tz;
1963
	},
1964
	/**
1965
	 * Compare two strings to provide a natural sort
1966
	 * @param a first string to compare
1967
	 * @param b second string to compare
1968
	 * @return -1 if b comes before a, 1 if a comes before b
1969
	 * or 0 if the strings are identical
1970
	 */
1971
	naturalSortCompare: function (a, b) {
1972
		var x;
1973
		var aa = OC.Util._chunkify(a);
1974
		var bb = OC.Util._chunkify(b);
1975
1976
		for (x = 0; aa[x] && bb[x]; x++) {
1977
			if (aa[x] !== bb[x]) {
1978
				var aNum = Number(aa[x]), bNum = Number(bb[x]);
1979
				// note: == is correct here
1980
				if (aNum == aa[x] && bNum == bb[x]) {
1981
					return aNum - bNum;
1982
				} else {
1983
					// Forcing 'en' locale to match the server-side locale which is
1984
					// always 'en'.
1985
					//
1986
					// Note: This setting isn't supported by all browsers but for the ones
1987
					// that do there will be more consistency between client-server sorting
1988
					return aa[x].localeCompare(bb[x], 'en');
1989
				}
1990
			}
1991
		}
1992
		return aa.length - bb.length;
1993
	},
1994
	/**
1995
	 * Calls the callback in a given interval until it returns true
1996
	 * @param {function} callback
1997
	 * @param {integer} interval in milliseconds
1998
	 */
1999
	waitFor: function (callback, interval) {
2000
		var internalCallback = function () {
2001
			if (callback() !== true) {
2002
				setTimeout(internalCallback, interval);
2003
			}
2004
		};
2005
2006
		internalCallback();
2007
	},
2008
	/**
2009
	 * Checks if a cookie with the given name is present and is set to the provided value.
2010
	 * @param {string} name name of the cookie
2011
	 * @param {string} value value of the cookie
2012
	 * @return {boolean} true if the cookie with the given name has the given value
2013
	 */
2014
	isCookieSetToValue: function (name, value) {
2015
		var cookies = document.cookie.split(';');
2016
		for (var i = 0; i < cookies.length; i++) {
2017
			var cookie = cookies[i].split('=');
2018
			if (cookie[0].trim() === name && cookie[1].trim() === value) {
2019
				return true;
2020
			}
2021
		}
2022
		return false;
2023
	},
2024
	
2025
	/**
2026
	 * Checks if an element is completely visible and scrolls the screen if not
2027
	 * @param {jQuery} jQuery element that has to be displayed 
2028
	 * @param {jQuery} scroll container if null scrollContainer will be set to $('#app-content')
2029
	 */
2030
	scrollIntoView: function (toViewEl, scrollContainer) {
2031
		
2032
		var toViewElTopLocation = toViewEl.offset().top;
2033
		var toViewElHeight = toViewEl.outerHeight();
2034
		var toViewElBottomLocation = toViewElTopLocation + toViewElHeight + 50;
2035
		var windowHeight = $(window).height();
2036
		
2037
		if (scrollContainer === null) {
2038
			scrollContainer = $('#app-content');
2039
		}
2040
2041
		if (toViewElBottomLocation > windowHeight) {
2042
			var currentPosition = scrollContainer[0].scrollTop;
2043
			var scrollDistance = toViewElBottomLocation - windowHeight;
2044
			scrollContainer.stop();
2045
			scrollContainer.animate({
2046
				scrollTop: currentPosition + scrollDistance
2047
			}, 700);
2048
		}
2049
	}
2050
};
2051
2052
/**
2053
 * Utility class for the history API,
2054
 * includes fallback to using the URL hash when
2055
 * the browser doesn't support the history API.
2056
 *
2057
 * @namespace
2058
 */
2059
OC.Util.History = {
2060
	_handlers: [],
2061
2062
	/**
2063
	 * Push the current URL parameters to the history stack
2064
	 * and change the visible URL.
2065
	 * Note: this includes a workaround for IE8/IE9 that uses
2066
	 * the hash part instead of the search part.
2067
	 *
2068
	 * @param params to append to the URL, can be either a string
2069
	 * or a map
2070
	 * @param {boolean} [replace=false] whether to replace instead of pushing
2071
	 */
2072
	_pushState: function (params, replace) {
2073
		var strParams;
2074
		if (typeof(params) === 'string') {
2075
			strParams = params;
2076
		}
2077
		else {
2078
			strParams = OC.buildQueryString(params);
2079
		}
2080
		if (window.history.pushState) {
2081
			var url = location.pathname + '?' + strParams;
2082
			if (replace) {
2083
				window.history.replaceState(params, '', url);
2084
			} else {
2085
				window.history.pushState(params, '', url);
2086
			}
2087
		}
2088
		// use URL hash for IE8
2089
		else {
2090
			window.location.hash = '?' + strParams;
2091
			// inhibit next onhashchange that just added itself
2092
			// to the event queue
2093
			this._cancelPop = true;
2094
		}
2095
	},
2096
2097
	/**
2098
	 * Push the current URL parameters to the history stack
2099
	 * and change the visible URL.
2100
	 * Note: this includes a workaround for IE8/IE9 that uses
2101
	 * the hash part instead of the search part.
2102
	 *
2103
	 * @param params to append to the URL, can be either a string
2104
	 * or a map
2105
	 */
2106
	pushState: function (params) {
2107
		return this._pushState(params, false);
2108
	},
2109
2110
	/**
2111
	 * Push the current URL parameters to the history stack
2112
	 * and change the visible URL.
2113
	 * Note: this includes a workaround for IE8/IE9 that uses
2114
	 * the hash part instead of the search part.
2115
	 *
2116
	 * @param params to append to the URL, can be either a string
2117
	 * or a map
2118
	 */
2119
	replaceState: function (params) {
2120
		return this._pushState(params, true);
2121
	},
2122
2123
	/**
2124
	 * Add a popstate handler
2125
	 *
2126
	 * @param handler function
2127
	 */
2128
	addOnPopStateHandler: function (handler) {
2129
		this._handlers.push(handler);
2130
	},
2131
2132
	/**
2133
	 * Parse a query string from the hash part of the URL.
2134
	 * (workaround for IE8 / IE9)
2135
	 */
2136
	_parseHashQuery: function () {
2137
		var hash = window.location.hash,
2138
			pos = hash.indexOf('?');
2139
		if (pos >= 0) {
2140
			return hash.substr(pos + 1);
2141
		}
2142
		if (hash.length) {
2143
			// remove hash sign
2144
			return hash.substr(1);
2145
		}
2146
		return '';
2147
	},
2148
2149
	_decodeQuery: function (query) {
2150
		return query.replace(/\+/g, ' ');
2151
	},
2152
2153
	/**
2154
	 * Parse the query/search part of the URL.
2155
	 * Also try and parse it from the URL hash (for IE8)
2156
	 *
2157
	 * @return map of parameters
2158
	 */
2159
	parseUrlQuery: function () {
2160
		var query = this._parseHashQuery(),
2161
			params;
2162
		// try and parse from URL hash first
2163
		if (query) {
2164
			params = OC.parseQueryString(this._decodeQuery(query));
2165
		}
2166
		// else read from query attributes
2167
		params = _.extend(params || {}, OC.parseQueryString(this._decodeQuery(location.search)));
2168
		return params || {};
2169
	},
2170
2171
	_onPopState: function (e) {
2172
		if (this._cancelPop) {
2173
			this._cancelPop = false;
2174
			return;
2175
		}
2176
		var params;
2177
		if (!this._handlers.length) {
2178
			return;
2179
		}
2180
		params = (e && e.state);
2181
		if (_.isString(params)) {
2182
			params = OC.parseQueryString(params);
2183
		} else if (!params) {
2184
			params = this.parseUrlQuery() || {};
2185
		}
2186
		for (var i = 0; i < this._handlers.length; i++) {
2187
			this._handlers[i](params);
2188
		}
2189
	}
2190
};
2191
2192
// fallback to hashchange when no history support
2193
if (window.history.pushState) {
2194
	window.onpopstate = _.bind(OC.Util.History._onPopState, OC.Util.History);
2195
}
2196
else {
2197
	$(window).on('hashchange', _.bind(OC.Util.History._onPopState, OC.Util.History));
2198
}
2199
2200
/**
2201
 * Get a variable by name
2202
 * @param {string} name
2203
 * @return {*}
2204
 */
2205
OC.get = function (name) {
2206
	var namespaces = name.split(".");
2207
	var tail = namespaces.pop();
2208
	var context = window;
2209
2210
	for (var i = 0; i < namespaces.length; i++) {
2211
		context = context[namespaces[i]];
2212
		if (!context) {
2213
			return false;
2214
		}
2215
	}
2216
	return context[tail];
2217
};
2218
2219
/**
2220
 * Set a variable by name
2221
 * @param {string} name
2222
 * @param {*} value
2223
 */
2224
OC.set = function (name, value) {
2225
	var namespaces = name.split(".");
2226
	var tail = namespaces.pop();
2227
	var context = window;
2228
2229
	for (var i = 0; i < namespaces.length; i++) {
2230
		if (!context[namespaces[i]]) {
2231
			context[namespaces[i]] = {};
2232
		}
2233
		context = context[namespaces[i]];
2234
	}
2235
	context[tail] = value;
2236
};
2237
2238
// fix device width on windows phone
2239
(function () {
2240
	if ("-ms-user-select" in document.documentElement.style && navigator.userAgent.match(/IEMobile\/10\.0/)) {
2241
		var msViewportStyle = document.createElement("style");
2242
		msViewportStyle.appendChild(
2243
			document.createTextNode("@-ms-viewport{width:auto!important}")
2244
		);
2245
		document.getElementsByTagName("head")[0].appendChild(msViewportStyle);
2246
	}
2247
})();
2248
2249
/**
2250
 * Namespace for apps
2251
 * @namespace OCA
2252
 */
2253
window.OCA = {};
2254
2255
/**
2256
 * select a range in an input field
2257
 * @link http://stackoverflow.com/questions/499126/jquery-set-cursor-position-in-text-area
2258
 * @param {type} start
2259
 * @param {type} end
2260
 */
2261
jQuery.fn.selectRange = function (start, end) {
2262
	return this.each(function () {
2263
		if (this.setSelectionRange) {
2264
			this.focus();
2265
			this.setSelectionRange(start, end);
2266
		} else if (this.createTextRange) {
2267
			var range = this.createTextRange();
2268
			range.collapse(true);
2269
			range.moveEnd('character', end);
2270
			range.moveStart('character', start);
2271
			range.select();
2272
		}
2273
	});
2274
};
2275
2276
/**
2277
 * check if an element exists.
2278
 * allows you to write if ($('#myid').exists()) to increase readability
2279
 * @link http://stackoverflow.com/questions/31044/is-there-an-exists-function-for-jquery
2280
 */
2281
jQuery.fn.exists = function () {
2282
	return this.length > 0;
2283
};
2284
2285
/**
2286
 * @deprecated use OC.Util.getScrollBarWidth() instead
2287
 */
2288
function getScrollBarWidth() {
2289
	return OC.Util.getScrollBarWidth();
2290
}
2291
2292
/**
2293
 * jQuery tipsy shim for the bootstrap tooltip
2294
 */
2295
jQuery.fn.tipsy = function (argument) {
2296
	console.warn('Deprecation warning: tipsy is deprecated. Use tooltip instead.');
2297
	if (typeof argument === 'object' && argument !== null) {
2298
2299
		// tipsy defaults
2300
		var options = {
2301
			placement: 'bottom',
2302
			delay: {'show': 0, 'hide': 0},
2303
			trigger: 'hover',
2304
			html: false,
2305
			container: 'body'
2306
		};
2307
		if (argument.gravity) {
2308
			switch (argument.gravity) {
2309
				case 'n':
2310
				case 'nw':
2311
				case 'ne':
2312
					options.placement = 'bottom';
2313
					break;
2314
				case 's':
2315
				case 'sw':
2316
				case 'se':
2317
					options.placement = 'top';
2318
					break;
2319
				case 'w':
2320
					options.placement = 'right';
2321
					break;
2322
				case 'e':
2323
					options.placement = 'left';
2324
					break;
2325
			}
2326
		}
2327
		if (argument.trigger) {
2328
			options.trigger = argument.trigger;
2329
		}
2330
		if (argument.delayIn) {
2331
			options.delay["show"] = argument.delayIn;
0 ignored issues
show
['show'] could be written in dot notation.

You can rewrite this statement in dot notation:

var obj = { };
obj['foo'] = 'bar'; // Bad
obj.foo = 'bar'; // Good
Loading history...
2332
		}
2333
		if (argument.delayOut) {
2334
			options.delay["hide"] = argument.delayOut;
0 ignored issues
show
['hide'] could be written in dot notation.

You can rewrite this statement in dot notation:

var obj = { };
obj['foo'] = 'bar'; // Bad
obj.foo = 'bar'; // Good
Loading history...
2335
		}
2336
		if (argument.html) {
2337
			options.html = true;
2338
		}
2339
		if (argument.fallback) {
2340
			options.title = argument.fallback;
2341
		}
2342
		// destroy old tooltip in case the title has changed
2343
		jQuery.fn.tooltip.call(this, 'destroy');
2344
		jQuery.fn.tooltip.call(this, options);
2345
	} else {
2346
		this.tooltip(argument);
2347
		jQuery.fn.tooltip.call(this, argument);
2348
	}
2349
	return this;
2350
}
2351