Completed
Push — 16.1 ( 295c38...d69fa7 )
by Hadi
15:59
created

AppJS.extend._share_link_callback   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 2
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
/**
2
 * EGroupware - Filemanager - Javascript UI
3
 *
4
 * @link http://www.egroupware.org
5
 * @package filemanager
6
 * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
7
 * @copyright (c) 2008-14 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
8
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
9
 * @version $Id$
10
 */
11
12
/**
13
 * UI for filemanager
14
 *
15
 * @augments AppJS
16
 */
17
app.classes.filemanager = AppJS.extend(
18
{
19
	appname: 'filemanager',
20
	/**
21
	 * path widget, by template
22
	 */
23
	path_widget: {},
24
	/**
25
	 * Are files cut into clipboard - need to be deleted at source on paste
26
	 */
27
	clipboard_is_cut: false,
28
29
	/**
30
	 * Constructor
31
	 *
32
	 * @memberOf app.filemanager
33
	 */
34
	init: function()
35
	{
36
		// call parent
37
		this._super.apply(this, arguments);
38
39
		// Loading filemanager in its tab and home causes us problems with
40
		// unwanted destruction, so we check for already existing path widgets
41
		var lists = etemplate2.getByApplication('home');
42
		for(var i = 0; i < lists.length; i++)
43
		{
44
			if(lists[i].app == 'filemanager' && lists[i].widgetContainer.getWidgetById('path'))
45
			{
46
				this.path_widget[lists[i].uniqueId] = lists[i].widgetContainer.getWidgetById('path');
47
			}
48
		}
49
	},
50
51
	/**
52
	 * Destructor
53
	 */
54
	destroy: function()
55
	{
56
		delete this.et2;
57
		// call parent
58
		this._super.apply(this, arguments);
59
	},
60
61
	/**
62
	 * This function is called when the etemplate2 object is loaded
63
	 * and ready.  If you must store a reference to the et2 object,
64
	 * make sure to clean it up in destroy().
65
	 *
66
	 * @param et2 etemplate2 Newly ready object
67
	 * @param {string} name template name
68
	 */
69
	et2_ready: function(et2,name)
70
	{
71
		// call parent
72
		this._super.apply(this, arguments);
73
74
		var path_widget = this.et2.getWidgetById('path');
75
		if(path_widget)	// do NOT set not found path-widgets, as uploads works on first one only!
76
		{
77
			this.path_widget[et2.DOMContainer.id] = path_widget;
78
			// Bind to removal to remove from list
79
			jQuery(et2.DOMContainer).on('clear', function(e) {
80
				if (app.filemanager && app.filemanager.path_widget) delete app.filemanager.path_widget[e.target.id];
81
			});
82
		}
83
84
		if(this.et2.getWidgetById('nm'))
85
		{
86
			// Legacy JS only supports 2 arguments (event and widget), so set
87
			// to the actual function here
88
			this.et2.getWidgetById('nm').set_onfiledrop(jQuery.proxy(this.filedrop, this));
89
		}
90
91
		// get clipboard from browser localstore and update button tooltips
92
		this.clipboard_tooltips();
93
94
		// calling set_readonly for initial path
95
		if (this.et2.getArrayMgr('content').getEntry('initial_path_readonly'))
96
		{
97
			this.readonly = [this.et2.getArrayMgr('content').getEntry('nm[path]'), true];
98
		}
99
		if (typeof this.readonly != 'undefined')
100
		{
101
			this.set_readonly.apply(this, this.readonly);
102
			delete this.readonly;
103
		}
104
	},
105
106
	/**
107
	 * Set the application's state to the given state.
108
	 *
109
	 * Extended from parent to also handle view
110
	 *
111
	 *
112
	 * @param {{name: string, state: object}|string} state Object (or JSON string) for a state.
113
	 *	Only state is required, and its contents are application specific.
114
	 *
115
	 * @return {boolean} false - Returns false to stop event propagation
116
	 */
117
	setState: function(state)
118
	{
119
		// State should be an object, not a string, but we'll parse
120
		if(typeof state == "string")
121
		{
122
			if(state.indexOf('{') != -1 || state =='null')
123
			{
124
				state = JSON.parse(state);
125
			}
126
		}
127
		var result = this._super.call(this, state, 'filemanager.index');
128
129
		// This has to happen after the parent, changing to tile recreates
130
		// nm controller
131
		if(typeof state == "object" && state.state && state.state.view)
132
		{
133
			var et2 = etemplate2.getById('filemanager-index');
134
			if(et2)
135
			{
136
				this.et2 = et2.widgetContainer;
137
				this.change_view(state.state.view);
138
			}
139
		}
140
		return result;
141
	},
142
143
	/**
144
	 * Retrieve the current state of the application for future restoration
145
	 *
146
	 * Extended from parent to also set view
147
	 *
148
	 * @return {object} Application specific map representing the current state
149
	 */
150
	getState: function()
151
	{
152
		var state = this._super.apply(this);
153
154
		var et2 = etemplate2.getById('filemanager-index');
155
		if(et2)
156
		{
157
			var nm = et2.widgetContainer.getWidgetById('nm');
158
			state.view = nm.view;
159
		}
160
161
		return state;
162
	},
163
164
	/**
165
	 * Regexp to convert id to a path, use this.id2path(_id)
166
	 */
167
	remove_prefix: /^filemanager::/,
168
	/**
169
	 * Convert id to path (remove "filemanager::" prefix)
170
	 *
171
	 * @param {string} _id
172
	 * @returns string
173
	 */
174
	id2path: function(_id)
175
	{
176
		return _id.replace(this.remove_prefix, '');
177
	},
178
179
	/**
180
	 * Convert array of elems to array of paths
181
	 *
182
	 * @param {egwActionObject[]} _elems selected items from actions
183
	 * @return array
184
	 */
185
	_elems2paths: function(_elems)
186
	{
187
		var paths = [];
188
		for (var i = 0; i < _elems.length; i++)
189
		{
190
			// If selected has no id, try parent.  This happens for the placeholder row
191
			// in empty directories.
192
			paths.push(_elems[i].id? this.id2path(_elems[i].id) : _elems[i]._context._parentId);
193
		}
194
		return paths;
195
	},
196
197
	/**
198
	 * Get directory of a path
199
	 *
200
	 * @param {string} _path
201
	 * @returns string
202
	 */
203
	dirname: function(_path)
204
	{
205
		var parts = _path.split('/');
206
		parts.pop();
207
		return parts.join('/') || '/';
208
	},
209
210
	/**
211
	 * Get name of a path
212
	 *
213
	 * @param {string} _path
214
	 * @returns string
215
	 */
216
	basename: function(_path)
217
	{
218
		return _path.split('/').pop();
219
	},
220
221
	/**
222
	 * Get current working directory
223
	 *
224
	 * @param {string} etemplate_name
225
	 * @return string
226
	 */
227
	get_path: function(etemplate_name)
228
	{
229
		if(!etemplate_name || typeof this.path_widget[etemplate_name] == 'undefined')
230
		{
231
			for(etemplate_name in this.path_widget) break;
232
		}
233
		return this.path_widget[etemplate_name] ? this.path_widget[etemplate_name].get_value() : null;
234
	},
235
236
	/**
237
	 * Open compose with already attached files
238
	 *
239
	 * @param {(string|string[])} attachments path(s)
240
	 * @param {object} params
241
	 */
242
	open_mail: function(attachments, params)
243
	{
244
		if (typeof attachments == 'undefined') attachments = this.get_clipboard_files();
245
		if (!params || typeof params != 'object') params = {};
246
		if (!(attachments instanceof Array)) attachments = [ attachments ];
247
		for(var i=0; i < attachments.length; i++)
248
		{
249
		   params['preset[file]['+i+']'] = 'vfs://default'+attachments[i];
250
		}
251
		// always open compose in html mode, as attachment links look a lot nicer in html
252
		params.mimeType = 'html';
253
		egw.open('', 'mail', 'add', params);
254
	},
255
256
	/**
257
	 * Mail files action: open compose with already attached files
258
	 *
259
	 * @param _action
260
	 * @param _elems
261
	 */
262
	mail: function(_action, _elems)
263
	{
264
		this.open_mail(this._elems2paths(_elems), {
265
			'preset[filemode]': _action.id.substr(5)
266
		});
267
	},
268
269
	/**
270
	 * Trigger Upload after each file is uploaded
271
	 * @param {type} _event
272
	 */
273
	uploadOnOne: function (_event)
274
	{
275
		this.upload(_event,1);
276
	},
277
278
	/**
279
	 * Send names of uploaded files (again) to server, to process them: either copy to vfs or ask overwrite/rename
280
	 *
281
	 * @param {event} _event
282
	 * @param {number} _file_count
283
	 * @param {string=} _path where the file is uploaded to, default current directory
284
	 */
285
	upload: function(_event, _file_count, _path)
286
	{
287
		if(typeof _path == 'undefined')
288
		{
289
			_path = this.get_path();
290
		}
291
		if (_file_count && !jQuery.isEmptyObject(_event.data.getValue()))
292
		{
293
			var widget = _event.data;
294
			var request = egw.json('filemanager_ui::ajax_action', ['upload', widget.getValue(), _path],
295
				this._upload_callback, this, true, this
296
			).sendRequest();
297
			widget.set_value('');
298
		}
299
	},
300
301
	/**
302
	 * Finish callback for file a file dialog, to get the overwrite / rename prompt
303
	 *
304
	 * @param {event} _event
305
	 * @param {number} _file_count
306
	 */
307
	file_a_file_upload: function(_event, _file_count)
308
	{
309
		var widget = _event.data;
310
		var value = widget.getValue();
311
		var path = widget.getRoot().getWidgetById("path").getValue();
312
		var action = widget.getRoot().getWidgetById("action").getValue();
313
		var link = widget.getRoot().getWidgetById("entry").getValue();
314
		if(action == 'save_as' && link.app && link.id)
315
		{
316
			path = "/apps/"+link.app+"/"+link.id;
317
		}
318
319
		var props = widget.getInstanceManager().getValues(widget.getRoot());
320
		egw.json('filemanager_ui::ajax_action', [action == 'save_as' ? 'upload' : 'link', widget.getValue(), path, props],
321
				function(_data)
322
				{
323
					app.filemanager._upload_callback(_data);
324
325
					// Remove successful after a delay
326
					for(var file in _data.uploaded)
327
					{
328
						if(!_data.uploaded[file].confirm || _data.uploaded[file].confirmed)
329
						{
330
							// Remove that file from file widget...
331
							widget.remove_file(_data.uploaded[file].name);
332
						}
333
					}
334
					opener.egw_refresh('','filemanager',null,null,'filemanager');
335
				}, app.filemanager, true, this
336
		).sendRequest(true);
337
		return true;
338
	},
339
340
341
	/**
342
	 * Callback for server response to upload request:
343
	 * - display message and refresh list
344
	 * - ask use to confirm overwritting existing files or rename upload
345
	 *
346
	 * @param {object} _data values for attributes msg, files, ...
347
	 */
348
	_upload_callback: function(_data)
349
	{
350
		if (_data.msg || _data.uploaded) window.egw_refresh(_data.msg, this.appname);
351
352
		var that = this;
353
		for(var file in _data.uploaded)
354
		{
355
			if (_data.uploaded[file].confirm && !_data.uploaded[file].confirmed)
356
			{
357
				var buttons = [
358
					{text: this.egw.lang("Yes"), id: "overwrite", class: "ui-priority-primary", "default": true, image: 'check',},
359
					{text: this.egw.lang("Rename"), id:"rename", image: 'edit',},
360
					{text: this.egw.lang("Cancel"), id:"cancel"}
361
				];
362
				if (_data.uploaded[file].confirm === "is_dir")
363
					buttons.shift();
364
				var dialog = et2_dialog.show_prompt(function(_button_id, _value) {
365
					var uploaded = {};
366
					uploaded[this.my_data.file] = this.my_data.data;
367
					switch (_button_id)
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
368
					{
369
						case "overwrite":
370
							uploaded[this.my_data.file].confirmed = true;
371
							// fall through
372
						case "rename":
373
							uploaded[this.my_data.file].name = _value;
374
							delete uploaded[this.my_data.file].confirm;
375
							// send overwrite-confirmation and/or rename request to server
376
							egw.json('filemanager_ui::ajax_action', [this.my_data.action, uploaded, this.my_data.path, this.my_data.props],
377
								that._upload_callback, that, true, that
378
							).sendRequest();
379
							return;
380
						case "cancel":
381
							// Remove that file from every file widget...
382
							that.et2.iterateOver(function(_widget) {
383
								_widget.remove_file(this.my_data.data.name);
384
							}, this, et2_file);
385
					}
386
				},
387
				_data.uploaded[file].confirm === "is_dir" ?
388
					this.egw.lang("There's already a directory with that name!") :
389
					this.egw.lang('Do you want to overwrite existing file %1 in directory %2?', _data.uploaded[file].name, _data.path),
390
				this.egw.lang('File %1 already exists', _data.uploaded[file].name),
391
				_data.uploaded[file].name, buttons, file);
392
				// setting required data for callback in as my_data
393
				dialog.my_data = {
394
					action: _data.action,
395
					file: file,
396
					path: _data.path,
397
					data: _data.uploaded[file],
398
					props: _data.props
399
				};
400
			}
401
		}
402
	},
403
404
	/**
405
	 * Get any files that are in the system clipboard
406
	 *
407
	 * @return {string[]} Paths
408
	 */
409
	get_clipboard_files: function()
410
	{
411
		var clipboard_files = [];
412
		if (typeof window.localStorage != 'undefined' && typeof egw.getSessionItem('phpgwapi', 'egw_clipboard') != 'undefined')
413
		{
414
			var clipboard = JSON.parse(egw.getSessionItem('phpgwapi', 'egw_clipboard')) || {
415
				type:[],
416
				selected:[]
417
			};
418
			if(clipboard.type.indexOf('file') >= 0)
419
			{
420
				for(var i = 0; i < clipboard.selected.length; i++)
421
				{
422
					var split = clipboard.selected[i].id.split('::');
423
					if(split[0] == 'filemanager')
424
					{
425
						clipboard_files.push(this.id2path(clipboard.selected[i].id));
426
					}
427
				}
428
			}
429
		}
430
		return clipboard_files;
431
	},
432
433
	/**
434
	 * Update clickboard tooltips in buttons
435
	 */
436
	clipboard_tooltips: function()
437
	{
438
		var paste_buttons = ['button[paste]', 'button[linkpaste]', 'button[mailpaste]'];
439
		for(var i=0; i < paste_buttons.length; ++i)
440
		{
441
			var button = this.et2.getWidgetById(paste_buttons[i]);
442
			if (button) button.set_statustext(this.get_clipboard_files().join(",\n"));
443
		}
444
	},
445
446
	/**
447
	 * Clip files into clipboard
448
	 *
449
	 * @param _action
450
	 * @param _elems
451
	 */
452
	clipboard: function(_action, _elems)
453
	{
454
		this.clipboard_is_cut = _action.id == "cut";
455
		var clipboard = JSON.parse(egw.getSessionItem('phpgwapi', 'egw_clipboard')) || {
456
			type:[],
457
			selected:[]
458
		};
459
		if(_action.id != "add")
460
		{
461
			clipboard = {
462
				type:[],
463
				selected:[]
464
			};
465
		};
466
467
		// When pasting we need to know the type of data - pull from actions
468
		var drag = _elems[0].getSelectedLinks('drag').links;
469
		for(var k in drag)
470
		{
471
			if(drag[k].enabled && drag[k].actionObj.dragType.length > 0)
472
			{
473
				clipboard.type = clipboard.type.concat(drag[k].actionObj.dragType);
474
			}
475
		}
476
		clipboard.type = jQuery.unique(clipboard.type);
477
		// egwAction is a circular structure and can't be stringified so just take what we want
478
		// Hopefully that's enough for the action handlers
479
		for(var k in _elems)
480
		{
481
			if(_elems[k].id) clipboard.selected.push({id:_elems[k].id, data:_elems[k].data});
482
		}
483
484
		// Save it in session
485
		egw.setSessionItem('phpgwapi', 'egw_clipboard', JSON.stringify(clipboard));
486
487
		this.clipboard_tooltips();
488
	},
489
490
	/**
491
	 * Paste files into current directory or mail them
492
	 *
493
	 * @param _type 'paste', 'linkpaste', 'mailpaste'
494
	 */
495
	paste: function(_type)
496
	{
497
		var clipboard_files = this.get_clipboard_files();
498
		if (clipboard_files.length == 0)
499
		{
500
			alert(this.egw.lang('Clipboard is empty!'));
501
			return;
502
		}
503
		switch(_type)
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
504
		{
505
			case 'mailpaste':
506
				this.open_mail(clipboard_files);
507
				break;
508
509
			case 'paste':
510
				this._do_action(this.clipboard_is_cut ? 'move' : 'copy', clipboard_files);
511
512
				if (this.clipboard_is_cut)
513
				{
514
					this.clipboard_is_cut = false;
515
					clipboard_files = [];
516
					this.clipboard_tooltips();
517
				}
518
				break;
519
520
			case 'linkpaste':
521
				this._do_action('symlink', clipboard_files);
522
				break;
523
		}
524
	},
525
526
	/**
527
	 * Pass action to server
528
	 *
529
	 * @param _action
530
	 * @param _elems
531
	 */
532
	action: function(_action, _elems)
533
	{
534
		var paths = this._elems2paths(_elems);
535
		var path = this.get_path(_action && _action.parent.data.nextmatch.getInstanceManager().uniqueId || false);
536
		this._do_action(_action.id, paths,true, path);
537
	},
538
539
	/**
540
	 * Prompt user for directory to create
541
	 *
542
	 * @param {egwAction|undefined|jQuery.Event} action Action, event or undefined if called directly
543
	 * @param {egwActionObject[] | undefined} selected Selected row, or undefined if called directly
544
	 */
545
	createdir: function(action, selected)
546
	{
547
		var self = this;
548
		et2_dialog.show_prompt(function(button, dir){
549
			if (button && dir)
550
			{
551
				var path = self.get_path(action && action.parent ? action.parent.data.nextmatch.getInstanceManager().uniqueId : false);
552
				if(action && action instanceof egwAction)
553
				{
554
					var paths = self._elems2paths(selected);
555
					if(paths[0]) path = paths[0];
556
					// check if target is a file --> use it's directory instead
557
					if(selected[0].id || path)
558
					{
559
						var data = egw.dataGetUIDdata(selected[0].id || 'filemanager::'+path );
560
						if (data && data.data.mime != 'httpd/unix-directory')
561
						{
562
							path = self.dirname(path);
563
						}
564
					}
565
				}
566
				self._do_action('createdir', dir, true, path);	// true=synchronous request
567
				self.change_dir((path == '/' ? '' : path)+'/'+ dir);
568
			}
569
		},this.egw.lang('New directory'),this.egw.lang('Create directory'));
570
	},
571
572
	/**
573
	 * Prompt user for directory to create
574
	 */
575
	symlink: function()
576
	{
577
		var self = this;
578
		et2_dialog.show_prompt(function (button, target) {
579
			if (button && target)
580
			{
581
				self._do_action('symlink', target);
582
			}
583
		},this.egw.lang('Link target'), this.egw.lang('Create link'));
584
	},
585
586
	/**
587
	 * Run a serverside action via an ajax call
588
	 *
589
	 * @param _type 'move_file', 'copy_file', ...
590
	 * @param _selected selected paths
591
	 * @param _sync send a synchronous ajax request
592
	 * @param _path defaults to current path
593
	 */
594
	_do_action: function(_type, _selected, _sync, _path)
595
	{
596
		if (typeof _path == 'undefined') _path = this.get_path();
597
		var request = egw.json('filemanager_ui::ajax_action', [_type, _selected, _path],
598
			this._do_action_callback, this, !_sync, this
599
		).sendRequest();
600
	},
601
602
	/**
603
	 * Callback for _do_action ajax call
604
	 *
605
	 * @param _data
606
	 */
607
	_do_action_callback: function(_data)
608
	{
609
		window.egw_refresh(_data.msg, this.appname);
610
	},
611
612
	/**
613
	 * Force download of a file by appending '?download' to it's download url
614
	 *
615
	 * @param _action
616
	 * @param _senders
617
	 */
618
	force_download: function(_action, _senders)
619
	{
620
		for(var i = 0; i < _senders.length; i++)
621
		{
622
			var data = egw.dataGetUIDdata(_senders[i].id);
623
			var url = data ? data.data.download_url : '/webdav.php'+this.id2path(_senders[i].id);
624
			if (url[0] == '/') url = egw.link(url);
625
626
			var a = document.createElement('a');
627
			if(typeof a.download == "undefined")
628
			{
629
				window.location = url+"?download";
630
				return false;
631
			}
632
633
			// Multiple file download for those that support it
634
			a = jQuery(a)
635
				.prop('href', url)
636
				.prop('download', data ? data.data.name : "")
637
				.appendTo(this.et2.getDOMNode());
638
639
			var evt = document.createEvent('MouseEvent');
640
			evt.initMouseEvent('click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
641
			a[0].dispatchEvent(evt);
642
			a.remove();
643
		}
644
		return false;
645
	},
646
647
	/**
648
	 * Check to see if the browser supports downloading multiple files
649
	 * (using a tag download attribute) to enable/disable the context menu
650
	 *
651
	 * @param {egwAction} action
652
	 * @param {egwActionObject[]} selected
653
	 */
654
	is_multiple_allowed: function(action, selected)
655
	{
656
		var allowed = typeof document.createElement('a').download != "undefined";
657
658
		if(typeof action == "undefined") return allowed;
659
660
		return (allowed || selected.length <= 1) && action.not_disableClass.apply(action, arguments);
661
	},
662
663
664
	/**
665
	 * Change directory
666
	 *
667
	 * @param {string} _dir directory to change to incl. '..' for one up
668
	 * @param {et2_widget} widget
669
	 */
670
	change_dir: function(_dir, widget)
671
	{
672
		for(var etemplate_name in this.path_widget) break;
673
		if (widget) etemplate_name = widget.getInstanceManager().uniqueId;
674
675
		switch (_dir)
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
676
		{
677
			case '..':
678
				_dir = this.dirname(this.get_path(etemplate_name));
0 ignored issues
show
Bug introduced by
The variable etemplate_name seems to not be initialized for all possible execution paths. Are you sure get_path handles undefined variables?
Loading history...
679
				break;
680
			case '~':
681
				_dir = this.et2.getWidgetById('nm').options.settings.home_dir;
682
				break;
683
		}
684
685
		this.path_widget[etemplate_name].set_value(_dir);
686
	},
687
688
	/**
689
	 * Toggle view between tiles and rows
690
	 *
691
	 * @param {string|Event} [view] - Specify what to change the view to.  Either 'tile' or 'row'.
692
	 *	Or, if this is used as a callback view is actually the event, and we need to find the view.
693
	 * @param {et2_widget} [button_widget] - The widget that's calling
694
	 */
695
	change_view: function(view, button_widget)
696
	{
697
		var et2 = etemplate2.getById('filemanager-index');
698
		var nm = false;
699
		if(et2 && et2.widgetContainer.getWidgetById('nm'))
700
		{
701
			nm = et2.widgetContainer.getWidgetById('nm');
702
		}
703
		if(!nm)
704
		{
705
			egw.debug('warn', 'Could not find nextmatch to change view');
706
707
			return;
708
		}
709
710
		if(!button_widget)
711
		{
712
			button_widget = nm.getWidgetById('button[change_view]');
713
		}
714
		if(button_widget && button_widget.instanceOf(et2_button))
715
		{
716
			// Switch view based on button icon, since controller can get re-created
717
			if(typeof view != 'string')
718
			{
719
				view = button_widget.options.image.replace('list_','');
720
			}
721
722
			// Toggle button icon to the other view
723
			button_widget.set_image("list_"+(view == nm.controller.VIEW_ROW ? nm.controller.VIEW_TILE : nm.controller.VIEW_ROW));
724
725
			button_widget.set_statustext(view == nm.controller.VIEW_ROW ? this.egw.lang("Tile view") : this.egw.lang('List view'));
726
		}
727
728
		nm.set_view(view);
729
		// Put it into active filters (but don't refresh)
730
		nm.activeFilters.view = view;
731
732
		// Change template to match
733
		var template = view == nm.controller.VIEW_ROW ? 'filemanager.index.rows' : 'filemanager.tile';
734
		nm.set_template(template);
735
736
		// Wait for template to load, then refresh
737
		template = nm.getWidgetById(template);
738
		if(template && template.loading)
739
		{
740
			template.loading.done(function() {
741
				nm.applyFilters({view: view});
742
			});
743
		}
744
	},
745
746
	/**
747
	 * Open/active an item
748
	 *
749
	 * @param _action
750
	 * @param _senders
751
	 */
752
	open: function(_action, _senders)
753
	{
754
		var data = egw.dataGetUIDdata(_senders[0].id);
755
		var path = this.id2path(_senders[0].id);
756
		var mime = this.et2._inst.widgetContainer.getWidgetById('$row');
757
		// try to get mime widget DOM node out of the row DOM
758
		var mime_dom = jQuery(_senders[0].iface.getDOMNode()).find("span#filemanager-index_\\$row");
759
760
		// symlinks dont have mime 'http/unix-directory', but server marks all directories with class 'isDir'
761
		if (data.data.mime == 'httpd/unix-directory' || data.data['class'] && data.data['class'].split(/ +/).indexOf('isDir') != -1)
762
		{
763
			this.change_dir(path,_action.parent.data.nextmatch || this.et2);
764
		}
765
		else if(mime && data.data.mime.match(mime.mime_regexp) && mime_dom.length>0)
766
		{
767
			mime_dom.click();
768
		}
769
		else
770
		{
771
			egw.open({path: path, type: data.data.mime, download_url: data.data.download_url}, 'file','view',null,'_browser');
772
		}
773
		return false;
774
	},
775
776
	/**
777
	 * Edit prefs of current directory
778
	 *
779
	 * @param _action
780
	 * @param _senders
781
	 */
782
	editprefs: function(_action, _senders)
783
	{
784
		var path =  typeof _senders != 'undefined' ? this.id2path(_senders[0].id) : this.get_path(_action && _action.parent.data.nextmatch.getInstanceManager().uniqueId || false);
785
786
		egw().open_link(egw.link('/index.php', {
787
			menuaction: 'filemanager.filemanager_ui.file',
788
			path: path
789
		}), 'fileprefs', '510x425');
790
	},
791
792
	/**
793
	 * Callback to check if the paste action is enabled.  We also update the
794
	 * clipboard historical targets here as well
795
	 *
796
	 * @param {egwAction} _action  drop action we're checking
797
	 * @param {egwActionObject[]} _senders selected files
798
	 * @param {egwActionObject} _target Drop or context menu activated on this one
799
	 *
800
	 * @returns boolean true if enabled, false otherwise
801
	 */
802
	paste_enabled: function paste_enabled(_action, _senders, _target)
803
	{
804
		// Need files in the clipboard for this
805
		var clipboard_files = this.get_clipboard_files();
806
		if(clipboard_files.length === 0)
807
		{
808
			return false;
809
		}
810
811
		// Parent action (paste) gets run through here as well, but needs no
812
		// further processing
813
		if(_action.id == 'paste') return true;
814
815
		if(_action.canHaveChildren.indexOf('drop') == -1)
816
		{
817
			_action.canHaveChildren.push('drop');
818
		}
819
		var actions = [];
820
		var dir = undefined;
0 ignored issues
show
Unused Code Comprehensibility introduced by
The assignment of undefined is not necessary as dir is implicitly marked as undefined by the declaration.
Loading history...
821
		var current_dir, target_dir = false;
822
823
		// Current directory
824
		current_dir = this.get_path();
825
		dir = egw.dataGetUIDdata('filemanager::'+current_dir);
826
		var path_widget = etemplate2.getById('filemanager-index').widgetContainer.getWidgetById('button[createdir]');
827
		actions.push({
828
			id:_action.id+'_current', caption: current_dir, path: current_dir,
829
			enabled: dir && dir.data && dir.data.class && dir.data.class.indexOf('noEdit') === -1 ||
830
					!dir && path_widget && !path_widget.options.readonly
831
		});
832
833
		// Target, if directory
834
		target_dir = this.id2path(_target.id);
835
		dir = egw.dataGetUIDdata(_target.id);
836
		actions.push({
837
				id: _action.id+'_target',
838
				caption: target_dir,
839
				path: target_dir,
840
				enabled: _target && _target.iface && jQuery(_target.iface.getDOMNode()).hasClass('isDir') &&
841
					(dir && dir.data && dir.data.class && dir.data.class.indexOf('noEdit') === -1 || !dir)
842
		});
843
844
		// Last 10 folders
845
		var previous_dsts = jQuery.extend([], egw.preference('drop_history',this.appname));
846
		var action_index = 0;
847
		for(var i = 0; i < 10; i++)
848
		{
849
			var path = i < previous_dsts.length ? previous_dsts[i] : '';
850
			actions.push({
851
				id: _action.id+'_target_'+action_index++,
852
				caption: path,
853
				path: path,
854
				group: 2,
855
				enabled: path && !(current_dir && path === current_dir || target_dir && path === target_dir)
856
			});
857
		}
858
859
		// Common stuff, every action needs these
860
		for(var i = 0; i < actions.length; i++)
861
		{
862
			//actions[i].type = 'drop',
863
			actions[i].acceptedTypes = _action.acceptedTypes;
864
			actions[i].no_lang = true;
865
			actions[i].hideOnDisabled = true;
866
		}
867
868
		_action.updateActions(actions);
869
870
		// Create paste action
871
		// This injects the clipboard data and calls the original handler
872
		var paste_exec = function(action, selected) {
873
			// Add in clipboard as a sender
874
			var clipboard = JSON.parse(egw.getSessionItem('phpgwapi', 'egw_clipboard'));
875
876
			// Set a flag so apps can tell the difference, if they need to
877
			action.set_onExecute(action.parent.onExecute.fnct);
878
			action.execute(clipboard.selected,selected[0]);
879
880
			// Clear the clipboard, the files are not there anymore
881
			if(action.id.indexOf('move') !== -1)
882
			{
883
				egw.setSessionItem('phpgwapi', 'egw_clipboard', JSON.stringify({
884
					type:[],
885
					selected:[]
886
				}));
887
			}
888
		};
889
		for(var i = 0; i < actions.length; i++)
890
		{
891
			_action.getActionById(actions[i].id).onExecute = jQuery.extend(true, {}, _action.onExecute);
892
893
			_action.getActionById(actions[i].id).set_onExecute(paste_exec);
894
		}
895
		return actions.length > 0;
896
	},
897
898
	/**
899
	 * File(s) droped
900
	 *
901
	 * @param _action
902
	 * @param _elems
903
	 * @param _target
904
	 * @returns
905
	 */
906
	drop: function(_action, _elems, _target)
907
	{
908
		var src = this._elems2paths(_elems);
909
910
911
		// Target will be missing ID if directory is empty
912
		// so start with the current directory
913
		var parent = _action;
914
		var nm = _target.manager.data.nextmatch;
915
		while(!nm && parent.parent)
916
		{
917
			parent = parent.parent;
918
			if(parent.data.nextmatch) nm = parent.data.nextmatch;
919
		}
920
		var nm_dst = this.get_path(nm.getInstanceManager().uniqueId || false);
921
922
		// Action specifies a destination, target does not matter
923
		if(_action.data && _action.data.path)
924
		{
925
			dst = _action.data.path;
926
		}
927
		// File(s) were dropped on a row, they want them inside
928
		else if(_target)
929
		{
930
			var dst = '';
931
			var paths = this._elems2paths([_target]);
932
			if(paths[0]) dst = paths[0];
933
934
			// check if target is a file --> use it's directory instead
935
			if(_target.id)
936
			{
937
				var data = egw.dataGetUIDdata(_target.id);
938
				if (!data || data.data.mime != 'httpd/unix-directory')
939
				{
940
					dst = this.dirname(dst);
941
				}
942
			}
943
		}
944
945
		// Remember the target for next time
946
		var previous_dsts = jQuery.extend([], egw.preference('drop_history',this.appname));
947
		previous_dsts.unshift(dst);
0 ignored issues
show
Bug introduced by
The variable dst does not seem to be initialized in case _target on line 928 is false. Are you sure the function unshift handles undefined variables?
Loading history...
948
		previous_dsts = Array.from(new Set(previous_dsts)).slice(0,9);
949
		egw.set_preference(this.appname, 'drop_history', previous_dsts);
950
951
		// Actual action id will be something like file_drop_{move|copy|link}[_other_id],
952
		// but we need to send move, copy or link
953
		var action_id = _action.id.replace("file_drop_",'').split('_',1)[0];
954
		this._do_action(action_id, src, false, dst || nm_dst);
955
	},
956
957
	/**
958
	 * Handle a native / HTML5 file drop from system
959
	 *
960
	 * This is a callback from nextmatch to prevent the default link action, and just upload instead.
961
	 *
962
	 * @param {string} row_uid UID of the row the files were dropped on
963
	 * @param {Files[]} files
964
	 */
965
	filedrop: function(row_uid, files)
966
	{
967
		var self = this;
968
		var data = egw.dataGetUIDdata(row_uid);
969
		files = files || window.event.dataTransfer.files;
970
971
		var path = typeof data != 'undefined' && data.data.mime == "httpd/unix-directory" ? data.data.path : this.get_path();
972
		var widget = this.et2.getWidgetById('upload');
973
974
		// Override finish to specify a potentially different path
975
		var old_onfinishone = widget.options.onFinishOne;
976
		var old_onfinish = widget.options.onFinish;
977
978
		widget.options.onFinishOne = function(_event, _file_count) {
979
			self.upload(_event, _file_count, path);
980
		};
981
982
		widget.options.onFinish = function() {
983
			widget.options.onFinish = old_onfinish;
984
			widget.options.onFinishOne = old_onfinishone;
985
		};
986
		// This triggers the upload
987
		widget.set_value(files);
988
989
		// Return false to prevent the link
990
		return false;
991
	},
992
993
	/**
994
	 * Change readonly state for given directory
995
	 *
996
	 * Get call/transported with each get_rows call, but should only by applied to UI if matching curent dir
997
	 *
998
	 * @param {string} _path
999
	 * @param {boolean} _ro
1000
	 */
1001
	set_readonly: function(_path, _ro)
1002
	{
1003
		//alert('set_readonly("'+_path+'", '+_ro+')');
1004
		if (!this.path_widget)	// widget not yet ready, try later
1005
		{
1006
			this.readonly = [_path, _ro];
1007
			return;
1008
		}
1009
		for(var id in this.path_widget)
1010
		{
1011
			var path = this.get_path(id);
1012
1013
			if (_path == path)
1014
			{
1015
				var ids = ['button[linkpaste]', 'button[paste]', 'button[createdir]', 'button[symlink]', 'upload'];
1016
				for(var i=0; i < ids.length; ++i)
1017
				{
1018
					var widget = etemplate2.getById(id).widgetContainer.getWidgetById(ids[i]);
1019
					if (widget)
1020
					{
1021
						widget.set_readonly(_ro);
1022
					}
1023
				}
1024
			}
1025
		}
1026
	},
1027
1028
	/**
1029
	 * Row or filename in select-file dialog clicked
1030
	 *
1031
	 * @param {jQuery.event} event
1032
	 * @param {et2_widget} widget
1033
	 */
1034
	select_clicked: function(event, widget)
1035
	{
1036
		if (!widget || typeof widget.value != 'object')
1037
		{
0 ignored issues
show
Comprehensibility Documentation Best Practice introduced by
This code block is empty. Consider removing it or adding a comment to explain.
Loading history...
1038
1039
		}
1040
		else if (widget.value.is_dir)	// true for "httpd/unix-directory" and "egw/*"
1041
		{
1042
			var path = null;
1043
			// Cannot do this, there are multiple widgets named path
1044
			// widget.getRoot().getWidgetById("path");
1045
			widget.getRoot().iterateOver(function(widget) {
1046
				if(widget.id == "path") path = widget;
1047
			},null, et2_textbox);
1048
			if(path)
1049
			{
1050
				path.set_value(widget.value.path);
1051
			}
1052
		}
1053
		else if (this.et2 && this.et2.getArrayMgr('content').getEntry('mode') != 'open-multiple')
1054
		{
1055
			var editfield = this.et2.getWidgetById('name');
1056
			if(editfield)
1057
			{
1058
				editfield.set_value(widget.value.name);
1059
			}
1060
		}
1061
		else
1062
		{
1063
			var file = widget.value.name;
1064
			widget.getParent().iterateOver(function(widget)
1065
			{
1066
				if(widget.options.selected_value == file)
1067
				{
1068
					widget.set_value(widget.get_value() == file ? widget.options.unselected_value : file);
1069
				}
1070
			}, null, et2_checkbox);
1071
1072
		}
1073
		// Stop event or it will toggle back off
1074
		event.preventDefault();
1075
		event.stopPropagation();
1076
		return false;
1077
	},
1078
1079
	/**
1080
	 * Set Sudo button's label and change its onclick handler according to its action
1081
	 *
1082
	 * @param {widget object} _widget sudo buttononly
1083
	 * @param {string} _action string of action type {login|logout}
1084
	 */
1085
	set_sudoButton: function (_widget, _action)
1086
	{
1087
		var widget = _widget || this.et2.getWidgetById('sudouser');
1088
		if (widget)
1089
		{
1090
			switch (_action)
1091
			{
1092
				case 'login':
1093
					widget.set_label('Logout');
1094
					this.et2._inst.submit(widget);
1095
					break;
1096
1097
				default:
1098
					widget.set_label('Superuser');
1099
					widget.onclick = function(){
1100
						jQuery('.superuser').css('display','inline');
1101
					};
1102
			}
1103
		}
1104
	},
1105
1106
	/**
1107
	 * Open file a file dialog from EPL, warn if EPL is not available
1108
	 *
1109
	 * @param {jQuery.event} _event
1110
	 * @param {et2_widget} _widget
1111
	 */
1112
	fileafile: function(_event, _widget)
1113
	{
1114
		if (this.egw.user('apps').stylite)
1115
		{
1116
			this.egw.open_link('/index.php?menuaction=stylite.stylite_filemanager.upload&path='+this.get_path(), '_blank', '670x320');
1117
		}
1118
		else
1119
		{
1120
			et2_dialog.show_dialog(function(_button)
1121
			{
1122
				if (_button == et2_dialog.YES_BUTTON) window.open('http://www.egroupware.org/EPL', '_blank');
1123
				return true;
1124
			}, this.egw.lang('File a file is only available with an EPL subscription.')+"\n\n"+
1125
				this.egw.lang('You can use regular upload [+] button to upload files.')+"\n\n"+
1126
				this.egw.lang('Do you want more information about EPL subscription?'),
1127
			this.egw.lang('File a file'), undefined, et2_dialog.BUTTONS_YES_NO, et2_dialog.QUESTION_MESSAGE);
1128
		}
1129
	},
1130
1131
	/**
1132
	 * create a share-link for the given file or directory
1133
         * @param {object} _action egw actions
1134
         * @param {object} _senders selected nm row
1135
         * @returns {Boolean} returns false if not successful
1136
	 */
1137
1138
	share_link: function(_action, _senders){
1139
		var data = egw.dataGetUIDdata(_senders[0].id)
1140
		var path = this.id2path(_senders[0].id);
1141
1142
                var request = egw.json('filemanager_ui::ajax_action', ['sharelink', path],
1143
                            this._share_link_callback, this, true, this
1144
                ).sendRequest();
1145
		return true;
1146
	},
1147
1148
1149
	/** 
1150
  	 * share-link callback
1151
	 */
1152
1153
	_share_link_callback: function(_data) {
1154
		if (_data.msg || _data.share_link) window.egw_refresh(_data.msg, this.appname);
1155
		console.log("_data", _data);
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
1156
1157
		var copy_link_to_clipboard = null;
1158
1159
		var copy_link_to_clipboard = function(evt){ 
1160
			var $target = jQuery(evt.target);
1161
			$target.select();
1162
			
1163
			console.log("share_link click");
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
1164
1165
			try {
1166
                                successful = document.execCommand('copy');
1167
                                if (successful)
1168
                                {
1169
                                        egw.message('Share link copied into clipboard');
1170
                                        return true;
1171
                                }
1172
                        }
1173
                        catch (e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
1174
                        egw.message('Failed to copy the link!');
1175
1176
		};
1177
		jQuery("body").on("click", "[name=share_link]", copy_link_to_clipboard);
1178
1179
                var dialog = et2_createWidget("dialog",{
1180
                        callback: function( button_id, value){ 
1181
			 	jQuery("body").off("click", "[name=share_link]", copy_link_to_clipboard);
1182
				return true; 
1183
			},
1184
                        title: "Share",
1185
                        template: _data.template,
1186
                        value: { content:{ "share_link": _data.share_link } }
1187
                });
1188
1189
	},
1190
1191
	/**
1192
	 * This function copies the selected file/folder entry as webdav link into clipboard
1193
	 *
1194
	 * @param {object} _action egw actions
1195
	 * @param {object} _senders selected nm row
1196
	 * @returns {Boolean} returns false if not successful
1197
	 */
1198
	copy_link: function (_action, _senders)
1199
	{
1200
		var data = egw.dataGetUIDdata(_senders[0].id);
1201
		var url = data ? data.data.download_url : '/webdav.php'+this.id2path(_senders[0].id);
1202
		if (url[0] == '/') url = egw.link(url);
1203
		if (url.substr(0,4) == 'http'  && url.indexOf('://') <= 5) {
1204
			// it's already a full url
1205
		}
1206
		else
1207
		{
1208
			var hostUrl = new URL(window.location.href);
1209
			url = hostUrl.origin + url;
1210
		}
1211
1212
		if (url)
1213
		{
1214
			var elem = jQuery(document.createElement('div'));
1215
			var range = [];
1216
			elem.text(url);
1217
			elem.appendTo('body');
1218
			if (document.selection)
1219
			{
1220
				range = document.body.createTextRange();
1221
				range.moveToElementText(elem);
1222
				range.select();
1223
			}
1224
			else if (window.getSelection)
1225
			{
1226
				var range = document.createRange();
1227
				range.selectNode(elem[0]);
1228
				window.getSelection().removeAllRanges();
1229
				window.getSelection().addRange(range);
1230
			}
1231
1232
			var successful = false;
1233
			try {
1234
				successful = document.execCommand('copy');
1235
				if (successful)
1236
				{
1237
					egw.message('WebDav link copied into clipboard');
1238
					window.getSelection().removeAllRanges();
1239
1240
					return true;
1241
				}
1242
			}
1243
			catch (e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
1244
			egw.message('Failed to copy the link!');
1245
			elem.remove();
1246
			return false;
1247
		}
1248
	}
1249
});
1250