Completed
Push — 16.1 ( 928d98...6e101a )
by Hadi
45:33 queued 28:58
created

filemanager_ui::get_mergeapp()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 27
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 16
nc 5
nop 2
dl 0
loc 27
rs 8.439
c 0
b 0
f 0
1
<?php
2
/**
3
 * EGroupware - Filemanager - user interface
4
 *
5
 * @link http://www.egroupware.org
6
 * @package filemanager
7
 * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
8
 * @copyright (c) 2008-17 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
9
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
10
 * @version $Id$
11
 */
12
13
use EGroupware\Api;
14
use EGroupware\Api\Link;
15
use EGroupware\Api\Framework;
16
use EGroupware\Api\Egw;
17
use EGroupware\Api\Etemplate;
18
19
use EGroupware\Api\Vfs;
20
21
/**
22
 * Filemanage user interface class
23
 */
24
class filemanager_ui
25
{
26
	/**
27
	 * Methods callable via menuaction
28
	 *
29
	 * @var array
30
	 */
31
	var $public_functions = array(
32
		'index' => true,
33
		'file' => true,
34
	);
35
36
	/**
37
	 * Views available from plugins
38
	 *
39
	 * @var array
40
	 */
41
	public static $views = array(
42
		'filemanager_ui::listview' => 'Listview',
43
	);
44
	public static $views_init = false;
45
46
	/**
47
	 * vfs namespace for document merge properties
48
	 *
49
	 */
50
	public static $merge_prop_namespace = '';
51
52
	/**
53
	 * Constructor
54
	 *
55
	 */
56
	function __construct()
57
	{
58
		// strip slashes from _GET parameters, if someone still has magic_quotes_gpc on
59
		if (get_magic_quotes_gpc() && $_GET)
60
		{
61
			$_GET = array_stripslashes($_GET);
62
		}
63
		// do we have root rights
64
		if (Api\Cache::getSession('filemanager', 'is_root'))
65
		{
66
			Vfs::$is_root = true;
67
		}
68
69
		static::init_views();
70
		static::$merge_prop_namespace = Vfs::DEFAULT_PROP_NAMESPACE.$GLOBALS['egw_info']['flags']['currentapp'];
71
	}
72
73
	/**
74
	 * Initialise and return available views
75
	 *
76
	 * @return array with method => label pairs
77
	 */
78
	public static function init_views()
79
	{
80
		if (!static::$views_init)
81
		{
82
			// translate our labels
83
			foreach(static::$views as &$label)
84
			{
85
				$label = lang($label);
86
			}
87
			// search for plugins with additional filemanager views
88
			foreach(Api\Hooks::process('filemanager_views') as $views)
89
			{
90
				if (is_array($views)) static::$views += $views;
91
			}
92
			static::$views_init = true;
93
		}
94
		return static::$views;
95
	}
96
97
	/**
98
	 * Get active view
99
	 *
100
	 * @return string
101
	 */
102
	public static function get_view()
103
	{
104
		$view =& Api\Cache::getSession('filemanager', 'view');
105
		if (isset($_GET['view']))
106
		{
107
			$view = $_GET['view'];
108
		}
109
		if (!isset(static::$views[$view]))
110
		{
111
			reset(static::$views);
112
			$view = key(static::$views);
113
		}
114
		return $view;
115
	}
116
117
	/**
118
	 * Context menu
119
	 *
120
	 * @return array
121
	 */
122
	public static function get_actions()
123
	{
124
		$actions = array(
125
			'open' => array(
126
				'caption' => lang('Open'),
127
				'icon' => '',
128
				'group' => $group=1,
129
				'allowOnMultiple' => false,
130
				'onExecute' => 'javaScript:app.filemanager.open',
131
				'default' => true
132
			),
133
			'saveas' => array(
134
				'caption' => lang('Save as'),
135
				'group' => $group,
136
				'allowOnMultiple' => true,
137
				'icon' => 'filesave',
138
				'onExecute' => 'javaScript:app.filemanager.force_download',
139
				'disableClass' => 'isDir',
140
				'enabled' => 'javaScript:app.filemanager.is_multiple_allowed'
141
			),
142
			'saveaszip' => array(
143
				'caption' => lang('Save as ZIP'),
144
				'group' => $group,
145
				'allowOnMultiple' => true,
146
				'icon' => 'save_zip',
147
				'postSubmit' => true
148
			),
149
			'edit' => array(
150
				'caption' => lang('Edit settings'),
151
				'group' => $group,
152
				'allowOnMultiple' => false,
153
				'onExecute' => Api\Header\UserAgent::mobile()?'javaScript:app.filemanager.viewEntry':'javaScript:app.filemanager.editprefs',
154
				'mobileViewTemplate' => 'file?'.filemtime(Api\Etemplate\Widget\Template::rel2path('/filemanager/templates/mobile/file.xet'))
155
			),
156
			'mkdir' => array(
157
				'caption' => lang('Create directory'),
158
				'icon' => 'filemanager/button_createdir',
159
				'group' => $group,
160
				'allowOnMultiple' => false,
161
				'onExecute' => 'javaScript:app.filemanager.createdir',
162
				'disableClass' => 'noEdit'
163
			),
164
			'mail' => array(
165
				'caption' => lang('Share files'),
166
				'icon' => 'filemanager/mail_post_to',
167
				'group' => $group,
168
				'children' => array(
169
					'sharelink' => array(
170
						'caption' => lang('Share link'),
171
						'group' => 1,
172
						'icon' => 'share',
173
						'allowOnMultiple' => false,
174
						'order' => 11,
175
						'onExecute' => 'javaScript:app.filemanager.share_link'
176
					)),
177
			),
178
			'egw_paste' => array(
179
				'enabled' => false,
180
				'group' => $group + 0.5,
181
				'hideOnDisabled' => true
182
			),
183
			'paste' => array(
184
				'caption' => lang('Paste'),
185
				'acceptedTypes' => 'file',
186
				'group' => $group + 0.5,
187
				'order' => 10,
188
				'enabled' => 'javaScript:app.filemanager.paste_enabled',
189
				'children' => array()
190
			),
191
			'copylink' => array(
192
				'caption' => lang('Copy link address'),
193
				'group' => $group + 0.5,
194
				'icon' => 'copy',
195
				'allowOnMultiple' => false,
196
				'order' => 10,
197
				'onExecute' => 'javaScript:app.filemanager.copy_link'
198
			),
199
			'documents' => filemanager_merge::document_action(
200
				$GLOBALS['egw_info']['user']['preferences']['filemanager']['document_dir'],
201
				++$group, 'Insert in document', 'document_',
202
				$GLOBALS['egw_info']['user']['preferences']['filemanager']['default_document']
203
			),
204
			'delete' => array(
205
				'caption' => lang('Delete'),
206
				'group' => ++$group,
207
				'confirm' => 'Delete these files or directories?',
208
				'onExecute' => 'javaScript:app.filemanager.action',
209
				'disableClass' => 'noDelete'
210
			),
211
			// DRAG and DROP events
212
			'file_drag' => array(
213
				'dragType' => array('file','link'),
214
				'type' => 'drag',
215
				'onExecute' => 'javaScript:app.filemanager.drag'
216
			),
217
			'file_drop_mail' => array(
218
				'type' => 'drop',
219
				'acceptedTypes' => 'mail',
220
				'onExecute' => 'javaScript:app.filemanager.drop',
221
				'hideOnDisabled' => true
222
			),
223
			'file_drop_move' => array(
224
				'icon' => 'stylite/move',
225
				'acceptedTypes' => 'file',
226
				'caption' => lang('Move into folder'),
227
				'type' => 'drop',
228
				'onExecute' => 'javaScript:app.filemanager.drop',
229
				'default' => true
230
			),
231
			'file_drop_copy' => array(
232
				'icon' => 'stylite/edit_copy',
233
				'acceptedTypes' => 'file',
234
				'caption' => lang('Copy into folder'),
235
				'type' => 'drop',
236
				'onExecute' => 'javaScript:app.filemanager.drop'
237
			),
238
			'file_drop_symlink' => array(
239
				'icon' => 'linkpaste',
240
				'acceptedTypes' => 'file',
241
				'caption' => lang('Link into folder'),
242
				'type' => 'drop',
243
				'onExecute' => 'javaScript:app.filemanager.drop'
244
			)
245
		);
246
		if (!isset($GLOBALS['egw_info']['user']['apps']['mail']))
247
		{
248
			unset($actions['mail']);
249
		}
250
		else
251
		{
252
			foreach(Vfs\Sharing::$modes as $mode => $data)
253
			{
254
				$actions['mail']['children']['mail_'.$mode] = array(
255
					'caption' => $data['label'],
256
					'hint' => $data['title'],
257
					'group' => 2,
258
					'onExecute' => 'javaScript:app.filemanager.mail',
259
				);
260
			}
261
		}
262
		// This would be done automatically, but we're overriding
263
		foreach($actions as $action_id => $action)
264
		{
265
			if($action['type'] == 'drop' && $action['caption'])
266
			{
267
				$action['type'] = 'popup';
268
				if($action['acceptedTypes'] == 'file')
269
				{
270
					$action['enabled'] = 'javaScript:app.filemanager.paste_enabled';
271
				}
272
				$actions['paste']['children']["{$action_id}_paste"] = $action;
273
			}
274
		}
275
		return $actions;
276
	}
277
278
	/**
279
	 * Get mergeapp property for given path
280
	 *
281
	 * @param string $path
282
	 * @param string $scope (default) or 'parents'
283
	 *    $scope == 'self' query only the given path
284
	 *    $scope == 'parents' query only path parents for property (first parent in hierarchy upwards wins)
285
	 *
286
	 * @return string merge application or NULL if no property found
287
	 */
288
	private static function get_mergeapp($path, $scope='self')
289
	{
290
		$app = null;
291
		switch($scope)
292
		{
293
			case 'self':
294
				$props = Vfs::propfind($path, static::$merge_prop_namespace);
295
				$app = empty($props) ? null : $props[0]['val'];
296
				break;
297
			case 'parents':
298
				// search for props in parent directories
299
				$currentpath = $path;
300
				while($dir = Vfs::dirname($currentpath))
301
				{
302
					$props = Vfs::propfind($dir, static::$merge_prop_namespace);
303
					if(!empty($props))
304
					{
305
						// found prop in parent directory
306
						return $app = $props[0]['val'];
307
					}
308
					$currentpath = $dir;
309
				}
310
				break;
311
		}
312
313
		return $app;
314
	}
315
316
	/**
317
	 * Main filemanager page
318
	 *
319
	 * @param array $content
320
	 * @param string $msg
321
	 */
322
	function index(array $content=null,$msg=null)
323
	{
324
		if (!is_array($content))
325
		{
326
			$content = array(
327
				'nm' => Api\Cache::getSession('filemanager', 'index'),
328
			);
329
			if (!is_array($content['nm']))
330
			{
331
				$content['nm'] = array(
332
					'get_rows'       =>	'filemanager.filemanager_ui.get_rows',	// I  method/callback to request the data for the rows eg. 'notes.bo.get_rows'
333
					'filter'         => '',	// current dir only
334
					'no_filter2'     => True,	// I  disable the 2. filter (params are the same as for filter)
335
					'no_cat'         => True,	// I  disable the cat-selectbox
336
					'lettersearch'   => True,	// I  show a lettersearch
337
					'searchletter'   =>	false,	// I0 active letter of the lettersearch or false for [all]
338
					'start'          =>	0,		// IO position in list
339
					'order'          =>	'name',	// IO name of the column to sort after (optional for the sortheaders)
340
					'sort'           =>	'ASC',	// IO direction of the sort: 'ASC' or 'DESC'
341
					'default_cols'   => '!comment,ctime',	// I  columns to use if there's no user or default pref (! as first char uses all but the named columns), default all columns
342
					'csv_fields'     =>	false, // I  false=disable csv export, true or unset=enable it with auto-detected fieldnames,
343
									//or array with name=>label or name=>array('label'=>label,'type'=>type) pairs (type is a eT widget-type)
344
					'row_id'         => 'path',
345
					'row_modified'   => 'mtime',
346
					'parent_id'      => 'dir',
347
					'is_parent'      => 'is_dir',
348
					'favorites'      => true,
349
					'placeholder_actions' => array('mkdir','paste','file_drop_mail','file_drop_move','file_drop_copy','file_drop_symlink')
350
				);
351
				$content['nm']['path'] = static::get_home_dir();
352
			}
353
			$content['nm']['actions'] = static::get_actions();
354
			$content['nm']['home_dir'] = static::get_home_dir();
355
			$content['nm']['view'] = $GLOBALS['egw_info']['user']['preferences']['filemanager']['nm_view'];
356
357
			if (isset($_GET['msg'])) $msg = $_GET['msg'];
358
359
			// Blank favorite set via GET needs special handling for path
360
			if (isset($_GET['favorite']) && $_GET['favorite'] == 'blank')
361
			{
362
				$content['nm']['path'] = static::get_home_dir();
363
			}
364
			// switch to projectmanager folders
365
			if (isset($_GET['pm_id']))
366
			{
367
				$_GET['path'] = '/apps/projectmanager'.((int)$_GET['pm_id'] ? '/'.(int)$_GET['pm_id'] : '');
368
			}
369
			if (isset($_GET['path']) && ($path = $_GET['path']))
370
			{
371
				switch($path)
372
				{
373
					case '..':
374
						$path = Vfs::dirname($content['nm']['path']);
375
						break;
376
					case '~':
377
						$path = static::get_home_dir();
378
						break;
379
				}
380
				if ($path && $path[0] == '/' && Vfs::stat($path,true) && Vfs::is_dir($path) && Vfs::check_access($path,Vfs::READABLE))
0 ignored issues
show
Bug Best Practice introduced by
The expression $path of type false|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
381
				{
382
					$content['nm']['path'] = $path;
383
				}
384
				else
385
				{
386
					$msg .= lang('The requested path %1 is not available.', $path ? Vfs::decodePath($path) : "false");
387
				}
388
				// reset lettersearch as it confuses users (they think the dir is empty)
389
				$content['nm']['searchletter'] = false;
390
				// switch recusive display off
391
				if (!$content['nm']['filter']) $content['nm']['filter'] = '';
392
			}
393
		}
394
		$view = static::get_view();
395
396
		call_user_func($view,$content,$msg);
397
	}
398
399
	/**
400
	 * Make the current user (vfs) root
401
	 *
402
	 * The user/pw is either the setup config user or a specially configured vfs_root user
403
	 *
404
	 * @param string $user setup config user to become root or '' to log off as root
405
	 * @param string $password setup config password to become root
406
	 * @param boolean &$is_setup=null on return true if authenticated user is setup config user, false otherwise
407
	 * @return boolean true is root user given, false otherwise (including logout / empty $user)
408
	 */
409
	protected function sudo($user='',$password=null,&$is_setup=null)
410
	{
411
		if (!$user)
412
		{
413
			$is_root = $is_setup = false;
414
		}
415
		else
416
		{
417
			// config user & password
418
			$is_setup = Api\Session::user_pw_hash($user,$password) === $GLOBALS['egw_info']['server']['config_hash'];
419
			// or vfs root user from setup >> configuration
420
			$is_root = $is_setup ||	$GLOBALS['egw_info']['server']['vfs_root_user'] &&
421
				in_array($user,preg_split('/, */',$GLOBALS['egw_info']['server']['vfs_root_user'])) &&
422
				$GLOBALS['egw']->auth->authenticate($user, $password, 'text');
423
		}
424
		//error_log(__METHOD__."('$user','$password',$is_setup) user_pw_hash(...)='".Api\Session::user_pw_hash($user,$password)."', config_hash='{$GLOBALS['egw_info']['server']['config_hash']}' --> returning ".array2string($is_root));
425
		Api\Cache::setSession('filemanager', 'is_setup',$is_setup);
426
		Api\Cache::setSession('filemanager', 'is_root',Vfs::$is_root = $is_root);
427
		return Vfs::$is_root;
428
	}
429
430
	/**
431
	 * Filemanager listview
432
	 *
433
	 * @param array $content
434
	 * @param string $msg
435
	 */
436
	function listview(array $content=null,$msg=null)
437
	{
438
		$tpl = new Etemplate('filemanager.index');
439
440
		if($msg) Framework::message($msg);
441
442
		if (($content['nm']['action'] || $content['nm']['rows']) && (empty($content['button']) || !isset($content['button'])))
443
		{
444
			if ($content['nm']['action'])
445
			{
446
				$msg = static::action($content['nm']['action'],$content['nm']['selected'],$content['nm']['path']);
447
				if($msg) Framework::message($msg);
448
449
				// clean up after action
450
				unset($content['nm']['selected']);
451
				// reset any occasion where action may be stored, as it may be ressurected out of the helpers by etemplate, which is quite unconvenient in case of action delete
452
				if (isset($content['nm']['action'])) unset($content['nm']['action']);
453
				if (isset($content['nm']['nm_action'])) unset($content['nm']['nm_action']);
454
				if (isset($content['nm_action'])) unset($content['nm_action']);
455
				// we dont use ['nm']['rows']['delete'], so unset it, if it is present
456
				if (isset($content['nm']['rows']['delete'])) unset($content['nm']['rows']['delete']);
457
			}
458
			elseif($content['nm']['rows']['delete'])
459
			{
460
				$msg = static::action('delete',array_keys($content['nm']['rows']['delete']),$content['nm']['path']);
461
				if($msg) Framework::message($msg);
462
463
				// clean up after action
464
				unset($content['nm']['rows']['delete']);
465
				// reset any occasion where action may be stored, as we use ['nm']['rows']['delete'] anyhow
466
				// we clean this up, as it may be ressurected out of the helpers by etemplate, which is quite unconvenient in case of action delete
467
				if (isset($content['nm']['action'])) unset($content['nm']['action']);
468
				if (isset($content['nm']['nm_action'])) unset($content['nm']['nm_action']);
469
				if (isset($content['nm_action'])) unset($content['nm_action']);
470
				if (isset($content['nm']['selected'])) unset($content['nm']['selected']);
471
			}
472
			unset($content['nm']['rows']);
473
			Api\Cache::setSession('filemanager', 'index',$content['nm']);
474
		}
475
476
		// be tolerant with (in previous versions) not correct urlencoded pathes
477
		if ($content['nm']['path'][0] == '/' && !Vfs::stat($content['nm']['path'],true) && Vfs::stat(urldecode($content['nm']['path'])))
478
		{
479
			$content['nm']['path'] = urldecode($content['nm']['path']);
480
		}
481
		if ($content['button'])
482
		{
483
			if ($content['button'])
484
			{
485
				list($button) = each($content['button']);
486
				unset($content['button']);
487
			}
488
			switch($button)
489
			{
490
				case 'upload':
491
					if (!$content['upload'])
492
					{
493
						Framework::message(lang('You need to select some files first!'),'error');
494
						break;
495
					}
496
					$upload_success = $upload_failure = array();
497
					foreach(isset($content['upload'][0]) ? $content['upload'] : array($content['upload']) as $upload)
498
					{
499
						// encode chars which special meaning in url/vfs (some like / get removed!)
500
						$to = Vfs::concat($content['nm']['path'],Vfs::encodePathComponent($upload['name']));
0 ignored issues
show
Bug introduced by
It seems like \EGroupware\Api\Vfs::enc...ponent($upload['name']) targeting EGroupware\Api\Vfs::encodePathComponent() can also be of type array; however, EGroupware\Api\Vfs::concat() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
501
						if ($upload &&
502
							(Vfs::is_writable($content['nm']['path']) || Vfs::is_writable($to)) &&
503
							copy($upload['tmp_name'],Vfs::PREFIX.$to))
504
						{
505
							$upload_success[] = $upload['name'];
506
						}
507
						else
508
						{
509
							$upload_failure[] = $upload['name'];
510
						}
511
					}
512
					$content['nm']['msg'] = '';
513
					if ($upload_success)
514
					{
515
						Framework::message( count($upload_success) == 1 && !$upload_failure ? lang('File successful uploaded.') :
516
							lang('%1 successful uploaded.',implode(', ',$upload_success)));
517
					}
518
					if ($upload_failure)
519
					{
520
						Framework::message(lang('Error uploading file!')."\n".etemplate::max_upload_size_message(),'error');
521
					}
522
					break;
523
			}
524
		}
525
		$readonlys['button[mailpaste]'] = !isset($GLOBALS['egw_info']['user']['apps']['mail']);
526
527
		$sel_options['filter'] = array(
528
			'' => 'Current directory',
529
			'2' => 'Directories sorted in',
530
			'3' => 'Show hidden files',
531
			'4' => 'All subdirectories',
532
			'5' => 'Files from links',
533
			'0'  => 'Files from subdirectories',
534
		);
535
		// sharing has no divAppbox, we need to set popupMainDiv instead, to be able to drop files everywhere
536
		if (substr($_SERVER['SCRIPT_FILENAME'], -10) == '/share.php')
537
		{
538
			$tpl->setElementAttribute('nm[buttons][upload]', 'drop_target', 'popupMainDiv');
539
		}
540
		// Set view button to match current settings
541
		if($content['nm']['view'] == 'tile')
542
		{
543
			$tpl->setElementAttribute('nm[button][change_view]','statustext',lang('List view'));
544
			$tpl->setElementAttribute('nm[button][change_view]','image','list_row');
545
		}
546
		// if initial load is done via GET request (idots template or share.php)
547
		// get_rows cant call app.filemanager.set_readonly, so we need to do that here
548
		$content['initial_path_readonly'] = !Vfs::is_writable($content['nm']['path']);
549
550
		$tpl->exec('filemanager.filemanager_ui.index',$content,$sel_options,$readonlys,array('nm' => $content['nm']));
551
	}
552
553
	/**
554
	 * Get the configured start directory for the current user
555
	 *
556
	 * @return string
557
	 */
558
	static function get_home_dir()
559
	{
560
		$start = '/home/'.$GLOBALS['egw_info']['user']['account_lid'];
561
562
		// check if user specified a valid startpath in his prefs --> use it
563
		if (($path = $GLOBALS['egw_info']['user']['preferences']['filemanager']['startfolder']) &&
564
			$path[0] == '/' && Vfs::is_dir($path) && Vfs::check_access($path, Vfs::READABLE))
565
		{
566
			$start = $path;
567
		}
568
		elseif (!Vfs::is_dir($start) && Vfs::check_access($start, Vfs::READABLE))
569
		{
570
			$start = '/';
571
		}
572
		return $start;
573
	}
574
575
	/**
576
	 * Run a certain action with the selected file
577
	 *
578
	 * @param string $action
579
	 * @param array $selected selected pathes
580
	 * @param mixed $dir current directory
581
	 * @param int &$errs=null on return number of errors
582
	 * @param int &$dirs=null on return number of dirs deleted
583
	 * @param int &$files=null on return number of files deleted
584
	 * @return string success or failure message displayed to the user
585
	 */
586
	static public function action($action,$selected,$dir=null,&$errs=null,&$files=null,&$dirs=null)
587
	{
588
		if (!count($selected))
589
		{
590
			return lang('You need to select some files first!');
591
		}
592
		$errs = $dirs = $files = 0;
593
594
		switch($action)
595
		{
596
597
			case 'delete':
598
				return static::do_delete($selected,$errs,$files,$dirs);
599
600
			case 'mail':
601
			case 'copy':
602
				foreach($selected as $path)
603
				{
604
					if (strpos($path, 'mail::') === 0 && $path = substr($path, 6))
605
					{
606
						// Support for dropping mail in filemanager - Pass mail back to mail app
607
						if(ExecMethod2('mail.mail_ui.vfsSaveMessage', $path, $dir, false))
0 ignored issues
show
Deprecated Code introduced by
The function ExecMethod2() has been deprecated with message: use autoloadable class-names, instanciate and call method or use static methods

This function has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.

Loading history...
608
						{
609
							++$files;
610
						}
611
						else
612
						{
613
							++$errs;
614
						}
615
					}
616
					elseif (!Vfs::is_dir($path))
617
					{
618
						$to = Vfs::concat($dir,Vfs::basename($path));
619
						if ($path != $to && Vfs::copy($path,$to))
620
						{
621
							++$files;
622
						}
623
						else
624
						{
625
							++$errs;
626
						}
627
					}
628
					else
629
					{
630
						$len = strlen(dirname($path));
631
						foreach(Vfs::find($path) as $p)
632
						{
633
							$to = $dir.substr($p,$len);
634
							if ($to == $p)	// cant copy into itself!
635
							{
636
								++$errs;
637
								continue;
638
							}
639
							if (($is_dir = Vfs::is_dir($p)) && Vfs::mkdir($to,null,STREAM_MKDIR_RECURSIVE))
640
							{
641
								++$dirs;
642
							}
643
							elseif(!$is_dir && Vfs::copy($p,$to))
644
							{
645
								++$files;
646
							}
647
							else
648
							{
649
								++$errs;
650
							}
651
						}
652
					}
653
				}
654
				if ($errs)
655
				{
656
					return lang('%1 errors copying (%2 diretories and %3 files copied)!',$errs,$dirs,$files);
657
				}
658
				return $dirs ? lang('%1 directories and %2 files copied.',$dirs,$files) : lang('%1 files copied.',$files);
659
660
			case 'move':
661
				foreach($selected as $path)
662
				{
663
					$to = Vfs::is_dir($dir) || count($selected) > 1 ? Vfs::concat($dir,Vfs::basename($path)) : $dir;
664
					if ($path != $to && Vfs::rename($path,$to))
665
					{
666
						++$files;
667
					}
668
					else
669
					{
670
						++$errs;
671
					}
672
				}
673
				if ($errs)
674
				{
675
					return lang('%1 errors moving (%2 files moved)!',$errs,$files);
676
				}
677
				return lang('%1 files moved.',$files);
678
679
			case 'symlink':	// symlink given files to $dir
680
				foreach((array)$selected as $target)
681
				{
682
					$link = Vfs::concat($dir, Vfs::basename($target));
683
					if (!Vfs::stat($dir) || ($ok = Vfs::mkdir($dir,0,true)))
684
					{
685
						if(!$ok)
686
						{
687
							$errs++;
688
							continue;
689
						}
690
					}
691
					if ($target[0] != '/') $target = Vfs::concat($dir, $target);
692
					if (!Vfs::stat($target))
693
					{
694
						return lang('Link target %1 not found!', Vfs::decodePath($target));
695
					}
696
					if ($target != $link && Vfs::symlink($target, $link))
697
					{
698
						++$files;
699
					}
700
					else
701
					{
702
						++$errs;
703
					}
704
				}
705
				if (count((array)$selected) == 1)
706
				{
707
					return $files ? lang('Symlink to %1 created.', Vfs::decodePath($target)) :
708
						lang('Error creating symlink to target %1!', Vfs::decodePath($target));
709
				}
710
				$ret = lang('%1 elements linked.', $files);
711
				if ($errs)
712
				{
713
					$ret = lang('%1 errors linking (%2)!',$errs, $ret);
714
				}
715
				return $ret;//." Vfs::symlink('$target', '$link')";
716
717
			case 'createdir':
718
				$dst = Vfs::concat($dir, is_array($selected) ? $selected[0] : $selected);
719
				if (Vfs::mkdir($dst, null, STREAM_MKDIR_RECURSIVE))
720
				{
721
					return lang("Directory successfully created.");
722
				}
723
				return lang("Error while creating directory.");
724
725
			case 'saveaszip':
726
				Vfs::download_zip($selected);
727
				exit;
728
729
			default:
730
				list($action, $settings) = explode('_', $action, 2);
731
				switch($action)
732
				{
733 View Code Duplication
					case 'document':
734
						if (!$settings) $settings = $GLOBALS['egw_info']['user']['preferences']['filemanager']['default_document'];
735
						$document_merge = new filemanager_merge(Vfs::decodePath($dir));
736
						$msg = $document_merge->download($settings, $selected, '', $GLOBALS['egw_info']['user']['preferences']['filemanager']['document_dir']);
737
						if($msg) return $msg;
738
						$errs = count($selected);
739
						return false;
740
				}
741
		}
742
		return "Unknown action '$action'!";
743
	}
744
745
	/**
746
	 * Delete selected files and return success or error message
747
	 *
748
	 * @param array $selected
749
	 * @param int &$errs=null on return number of errors
750
	 * @param int &$dirs=null on return number of dirs deleted
751
	 * @param int &$files=null on return number of files deleted
752
	 * @return string
753
	 */
754
	public static function do_delete(array $selected, &$errs=null, &$dirs=null, &$files=null)
755
	{
756
		$dirs = $files = $errs = 0;
757
		// we first delete all selected links (and files)
758
		// feeding the links to dirs to Vfs::find() deletes the content of the dirs, not just the link!
759
		foreach($selected as $key => $path)
760
		{
761
			if (!Vfs::is_dir($path) || Vfs::is_link($path))
762
			{
763
				if (Vfs::unlink($path))
764
				{
765
					++$files;
766
				}
767
				else
768
				{
769
					++$errs;
770
				}
771
				unset($selected[$key]);
772
			}
773
		}
774
		if ($selected)	// somethings left to delete
775
		{
776
			// some precaution to never allow to (recursivly) remove /, /apps or /home
777
			foreach((array)$selected as $path)
778
			{
779
				if (Vfs::isProtectedDir($path))
780
				{
781
					$errs++;
782
					return lang("Cautiously rejecting to remove folder '%1'!",Vfs::decodePath($path));
783
				}
784
			}
785
			// now we use find to loop through all files and dirs: (selected only contains dirs now)
786
			// - depth=true to get first the files and then the dir containing it
787
			// - hidden=true to also return hidden files (eg. Thumbs.db), as we cant delete non-empty dirs
788
			// - show-deleted=false to not (finally) deleted versioned files
789
			foreach(Vfs::find($selected,array('depth'=>true,'hidden'=>true,'show-deleted'=>false)) as $path)
790
			{
791
				if (($is_dir = Vfs::is_dir($path) && !Vfs::is_link($path)) && Vfs::rmdir($path,0))
0 ignored issues
show
Unused Code introduced by
The call to Vfs::rmdir() has too many arguments starting with 0.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
Comprehensibility introduced by
Consider adding parentheses for clarity. Current Interpretation: $is_dir = (\EGroupware\A...pi\Vfs::is_link($path)), Probably Intended Meaning: ($is_dir = \EGroupware\A...Api\Vfs::is_link($path)
Loading history...
792
				{
793
					++$dirs;
794
				}
795
				elseif (!$is_dir && Vfs::unlink($path))
796
				{
797
					++$files;
798
				}
799
				else
800
				{
801
					++$errs;
802
				}
803
			}
804
		}
805
		if ($errs)
806
		{
807
			return lang('%1 errors deleteting (%2 directories and %3 files deleted)!',$errs,$dirs,$files);
808
		}
809
		if ($dirs)
810
		{
811
			return lang('%1 directories and %2 files deleted.',$dirs,$files);
812
		}
813
		return $files == 1 ? lang('File deleted.') : lang('%1 files deleted.',$files);
814
	}
815
816
	/**
817
	 * Callback to fetch the rows for the nextmatch widget
818
	 *
819
	 * @param array $query
820
	 * @param array &$rows
821
	 */
822
	function get_rows(&$query, &$rows)
823
	{
824
		// do NOT store query, if hierarchical data / children are requested
825 View Code Duplication
		if (!$query['csv_export'])
826
		{
827
			Api\Cache::setSession('filemanager', 'index',
828
				array_diff_key ($query, array_flip(array('rows','actions','action_links','placeholder_actions'))));
829
		}
830
		if(!$query['path']) $query['path'] = static::get_home_dir();
831
832
		// Change template to match selected view
833
		if($query['view'])
834
		{
835
			$query['template'] = ($query['view'] == 'row' ? 'filemanager.index.rows' : 'filemanager.tile');
836
837
			// Store as preference but only for index, not home
838
			if($query['get_rows'] == 'filemanager.filemanager_ui.get_rows')
839
			{
840
				$GLOBALS['egw']->preferences->add('filemanager','nm_view',$query['view']);
841
				$GLOBALS['egw']->preferences->save_repository();
842
			}
843
		}
844
		// be tolerant with (in previous versions) not correct urlencoded pathes
845
		if (!Vfs::stat($query['path'],true) && Vfs::stat(urldecode($query['path'])))
846
		{
847
			$query['path'] = urldecode($query['path']);
848
		}
849
		if (!Vfs::stat($query['path'],true) || !Vfs::is_dir($query['path']) || !Vfs::check_access($query['path'],Vfs::READABLE))
850
		{
851
			// only redirect, if it would be to some other location, gives redirect-loop otherwise
852
			if ($query['path'] != ($path = static::get_home_dir()))
853
			{
854
				// we will leave here, since we are not allowed, or the location does not exist. Index must handle that, and give
855
				// an appropriate message
856
				Egw::redirect_link('/index.php',array('menuaction'=>'filemanager.filemanager_ui.index',
857
					'path' => $path,
858
					'msg' => lang('The requested path %1 is not available.',Vfs::decodePath($query['path'])),
859
					'ajax' => 'true'
860
				));
861
			}
862
			$rows = array();
863
			return 0;
864
		}
865
		$rows = $dir_is_writable = array();
866
		if($query['searchletter'] && !empty($query['search']))
867
		{
868
			$namefilter = '/^'.$query['searchletter'].'.*'.str_replace(array('\\?','\\*'),array('.{1}','.*'),preg_quote($query['search'])).'/i';
869
			if ($query['searchletter'] == strtolower($query['search'][0]))
870
			{
871
				$namefilter = '/^('.$query['searchletter'].'.*'.str_replace(array('\\?','\\*'),array('.{1}','.*'),preg_quote($query['search'])).'|'.
872
					str_replace(array('\\?','\\*'),array('.{1}','.*'),preg_quote($query['search'])).')/i';
873
			}
874
		}
875
		elseif ($query['searchletter'])
876
		{
877
			$namefilter = '/^'.$query['searchletter'].'/i';
878
		}
879
		elseif(!empty($query['search']))
880
		{
881
			$namefilter = '/'.str_replace(array('\\?','\\*'),array('.{1}','.*'),preg_quote($query['search'])).'/i';
882
		}
883
884
		// Re-map so 'No filters' favorite ('') is depth 1
885
		$filter = $query['filter'] === '' ? 1 : $query['filter'];
886
887
		$maxdepth = $filter && $filter != 4 ? (int)(boolean)$filter : null;
888
		if($filter == 5) $maxdepth = 2;
889
		$n = 0;
890
		$vfs_options = array(
891
			'mindepth' => 1,
892
			'maxdepth' => $maxdepth,
893
			'dirsontop' => $filter <= 1,
894
			'type' => $filter && $filter != 5 ? ($filter == 4 ? 'd' : null) : ($filter == 5 ? 'F':'f'),
895
			'order' => $query['order'], 'sort' => $query['sort'],
896
			'limit' => (int)$query['num_rows'].','.(int)$query['start'],
897
			'need_mime' => true,
898
			'name_preg' => $namefilter,
899
			'hidden' => $filter == 3,
900
			'follow' => $filter == 5,
901
		);
902
		if($query['col_filter']['mime'])
903
		{
904
			$vfs_options['mime'] = $query['col_filter']['mime'];
905
		}
906
		foreach(Vfs::find(!empty($query['col_filter']['dir']) ? $query['col_filter']['dir'] : $query['path'],$vfs_options,true) as $path => $row)
907
		{
908
			//echo $path; _debug_array($row);
909
910
			$dir = dirname($path);
911
			if (!isset($dir_is_writable[$dir]))
912
			{
913
				$dir_is_writable[$dir] = Vfs::is_writable($dir);
914
			}
915
			if (Vfs::is_dir($path))
916
			{
917
				if (!isset($dir_is_writable[$path]))
918
				{
919
					$dir_is_writable[$path] = Vfs::is_writable($path);
920
				}
921
922
				$row['class'] .= 'isDir ';
923
				$row['is_dir'] = 1;
924
			}
925
			if(!$dir_is_writable[$path])
926
			{
927
				$row['class'] .= 'noEdit ';
928
			}
929
			$row['class'] .= !$dir_is_writable[$dir] ? 'noDelete' : '';
930
			$row['download_url'] = Vfs::download_url($path);
931
			$row['gid'] = -abs($row['gid']);	// gid are positive, but we use negagive account_id for groups internal
932
933
			$rows[++$n] = $row;
934
			$path2n[$path] = $n;
935
		}
936
		// query comments and cf's for the displayed rows
937
		$cols_to_show = explode(',',$GLOBALS['egw_info']['user']['preferences']['filemanager']['nextmatch-filemanager.index.rows']);
938
939
		// Always include comment in tiles
940
		if($query['view'] == 'tile')
941
		{
942
			$cols_to_show[] = 'comment';
943
		}
944
		$all_cfs = in_array('customfields',$cols_to_show) && $cols_to_show[count($cols_to_show)-1][0] != '#';
945
		if ($path2n && (in_array('comment',$cols_to_show) || in_array('customfields',$cols_to_show)) &&
946
			($path2props = Vfs::propfind(array_keys($path2n))))
947
		{
948
			foreach($path2props as $path => $props)
0 ignored issues
show
Bug introduced by
The expression $path2props of type array|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
949
			{
950
				unset($row);	// fixes a weird problem with php5.1, does NOT happen with php5.2
951
				$row =& $rows[$path2n[$path]];
952
				if ( !is_array($props) ) continue;
953
				foreach($props as $prop)
954
				{
955
					if (!$all_cfs && $prop['name'][0] == '#' && !in_array($prop['name'],$cols_to_show)) continue;
956
					$row[$prop['name']] = strlen($prop['val']) < 64 ? $prop['val'] : substr($prop['val'],0,64).' ...';
957
				}
958
			}
959
		}
960
		// tell client-side if directory is writeable or not
961
		$response = Api\Json\Response::get();
962
		$response->call('app.filemanager.set_readonly', $query['path'], !Vfs::is_writable($query['path']));
963
964
		//_debug_array($readonlys);
965
		if ($GLOBALS['egw_info']['flags']['currentapp'] == 'projectmanager')
966
		{
967
			// we need our app.css file
968 View Code Duplication
			if (!file_exists(EGW_SERVER_ROOT.($css_file='/filemanager/templates/'.$GLOBALS['egw_info']['server']['template_set'].'/app.css')))
969
			{
970
				$css_file = '/filemanager/templates/default/app.css';
971
			}
972
			$GLOBALS['egw_info']['flags']['css'] .= "\n\t\t</style>\n\t\t".'<link href="'.$GLOBALS['egw_info']['server']['webserver_url'].
973
				$css_file.'?'.filemtime(EGW_SERVER_ROOT.$css_file).'" type="text/css" rel="StyleSheet" />'."\n\t\t<style>\n\t\t\t";
974
		}
975
		return Vfs::$find_total;
976
	}
977
978
	/**
979
	 * Preferences of a file/directory
980
	 *
981
	 * @param array $content
982
	 * @param string $msg
983
	 */
984
	function file(array $content=null,$msg='')
985
	{
986
		$tpl = new Etemplate('filemanager.file');
987
988
		if (!is_array($content))
989
		{
990
			if (isset($_GET['msg']))
991
			{
992
				$msg .= $_GET['msg'];
993
			}
994
			if (!($path = str_replace(array('#','?'),array('%23','%3F'),$_GET['path'])) ||	// ?, # need to stay encoded!
995
				// actions enclose pathes containing comma with "
996
				($path[0] == '"' && substr($path,-1) == '"' && !($path = substr(str_replace('""','"',$path),1,-1))) ||
997
				!($stat = Vfs::lstat($path)))
998
			{
999
				$msg .= lang('File or directory not found!')." path='$path', stat=".array2string($stat);
1000
			}
1001
			else
1002
			{
1003
				$content = $stat;
1004
				$content['name'] = $content['itempicker_merge']['name'] = Vfs::basename($path);
1005
				$content['dir'] = $content['itempicker_merge']['dir'] = ($dir = Vfs::dirname($path)) ? Vfs::decodePath($dir) : '';
1006
				$content['path'] = $path;
1007
				$content['hsize'] = Vfs::hsize($stat['size']);
1008
				$content['mime'] = Vfs::mime_content_type($path);
1009
				$content['gid'] *= -1;	// our widgets use negative gid's
1010 View Code Duplication
				if (($props = Vfs::propfind($path)))
1011
				{
1012
					foreach($props as $prop)
0 ignored issues
show
Bug introduced by
The expression $props of type array|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1013
					{
1014
						$content[$prop['name']] = $prop['val'];
1015
					}
1016
				}
1017
				if (($content['is_link'] = Vfs::is_link($path)))
1018
				{
1019
					$content['symlink'] = Vfs::readlink($path);
1020
				}
1021
			}
1022
			$content['tabs'] = $_GET['tabs'];
1023
			if (!($content['is_dir'] = Vfs::is_dir($path) && !Vfs::is_link($path)))
0 ignored issues
show
Comprehensibility introduced by
Consider adding parentheses for clarity. Current Interpretation: $content['is_dir'] = (\E...pi\Vfs::is_link($path)), Probably Intended Meaning: ($content['is_dir'] = \E...Api\Vfs::is_link($path)
Loading history...
1024
			{
1025
				$content['perms']['executable'] = (int)!!($content['mode'] & 0111);
1026
				$mask = 6;
1027
				if (preg_match('/^text/',$content['mime']) && $content['size'] < 100000)
1028
				{
1029
					$content['text_content'] = file_get_contents(Vfs::PREFIX.$path);
1030
				}
1031
			}
1032
			else
1033
			{
1034
				//currently not implemented in backend $content['perms']['sticky'] = (int)!!($content['mode'] & 0x201);
1035
				$mask = 7;
1036
			}
1037
			foreach(array('owner' => 6,'group' => 3,'other' => 0) as $name => $shift)
1038
			{
1039
				$content['perms'][$name] = ($content['mode'] >> $shift) & $mask;
1040
			}
1041
			$content['is_owner'] = Vfs::has_owner_rights($path,$content);
1042
		}
1043
		else
1044
		{
1045
			//_debug_array($content);
1046
			$path =& $content['path'];
1047
1048
			list($button) = @each($content['button']); unset($content['button']);
1049
			if(!$button && $content['sudo'])
1050
			{
1051
				// Button to stop sudo is not in button namespace
1052
				$button = 'sudo';
1053
				unset($content['sudo']);
1054
			}
1055
			// need to check 'setup' button (submit button in sudo popup), as some browsers (eg. chrome) also fill the hidden field
1056
			if ($button == 'sudo' && Vfs::$is_root || $button == 'setup' && $content['sudo']['user'])
1057
			{
1058
				$msg = $this->sudo($button == 'setup' ? $content['sudo']['user'] : '',$content['sudo']['passwd']) ?
1059
					lang('Root access granted.') : ($button == 'setup' && $content['sudo']['user'] ?
1060
					lang('Wrong username or password!') : lang('Root access stopped.'));
1061
				unset($content['sudo']);
1062
				$content['is_owner'] = Vfs::has_owner_rights($path);
1063
			}
1064
			if (in_array($button,array('save','apply')))
1065
			{
1066
				$props = array();
1067
				$perm_changed = $perm_failed = 0;
1068
				foreach($content['old'] as $name => $old_value)
1069
				{
1070
					if (isset($content[$name]) && ($old_value != $content[$name] ||
1071
						// do not check for modification, if modify_subs is checked!
1072
						$content['modify_subs'] && in_array($name,array('uid','gid','perms'))) &&
1073
						($name != 'uid' || Vfs::$is_root))
1074
					{
1075
						if ($name == 'name')
1076
						{
1077
							if (!($dir = Vfs::dirname($path)))
1078
							{
1079
								$msg .= lang('File or directory not found!')." Vfs::dirname('$path')===false";
1080
								if ($button == 'save') $button = 'apply';
1081
								continue;
1082
							}
1083
							$to = Vfs::concat($dir, $content['name']);
1084
							if (file_exists(Vfs::PREFIX.$to) && $content['confirm_overwrite'] !== $to)
1085
							{
1086
								$tpl->set_validation_error('name',lang("There's already a file with that name!").'<br />'.
1087
									lang('To overwrite the existing file store again.',lang($button)));
1088
								$content['confirm_overwrite'] = $to;
1089
								if ($button == 'save') $button = 'apply';
1090
								continue;
1091
							}
1092
							if (Vfs::rename($path,$to))
1093
							{
1094
								$msg .= lang('Renamed %1 to %2.',Vfs::decodePath(basename($path)),Vfs::decodePath(basename($to))).' ';
1095
								$content['old']['name'] = $content[$name];
1096
								$path = $to;
1097
								$content['mime'] = Api\MimeMagic::filename2mime($path);	// recheck mime type
1098
								$refresh_path = Vfs::dirname($path);	// for renames, we have to refresh the parent
1099
							}
1100
							else
1101
							{
1102
								$msg .= lang('Rename of %1 to %2 failed!',Vfs::decodePath(basename($path)),Vfs::decodePath(basename($to))).' ';
1103
								if (Vfs::deny_script($to))
1104
								{
1105
									$msg .= lang('You are NOT allowed to upload a script!').' ';
1106
								}
1107
							}
1108
						}
1109
						elseif ($name[0] == '#' || $name == 'comment')
1110
						{
1111
							$props[] = array('name' => $name, 'val' => $content[$name] ? $content[$name] : null);
1112
						}
1113
						elseif ($name == 'mergeapp')
1114
						{
1115
							$mergeprop = array(
1116
								array(
1117
									'ns'	=> static::$merge_prop_namespace,
1118
									'name'	=> 'mergeapp',
1119
									'val'	=> (!empty($content[$name]) ? $content[$name] : null),
1120
								),
1121
							);
1122 View Code Duplication
							if (Vfs::proppatch($path,$mergeprop))
1123
							{
1124
								$content['old'][$name] = $content[$name];
1125
								$msg .= lang('Setting for document merge saved.');
1126
							}
1127
							else
1128
							{
1129
								$msg .= lang('Saving setting for document merge failed!');
1130
							}
1131
						}
1132
						else
1133
						{
1134
							static $name2cmd = array('uid' => 'chown','gid' => 'chgrp','perms' => 'chmod');
1135
							$cmd = array('EGroupware\\Api\\Vfs',$name2cmd[$name]);
1136
							$value = $name == 'perms' ? static::perms2mode($content['perms']) : $content[$name];
0 ignored issues
show
Bug introduced by
Since perms2mode() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of perms2mode() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
1137
							if ($content['modify_subs'])
1138
							{
1139
								if ($name == 'perms')
1140
								{
1141
									$changed = Vfs::find($path,array('type'=>'d'),$cmd,array($value));
1142
									$changed += Vfs::find($path,array('type'=>'f'),$cmd,array($value & 0666));	// no execute for files
1143
								}
1144
								else
1145
								{
1146
									$changed = Vfs::find($path,null,$cmd,array($value));
1147
								}
1148
								$ok = $failed = 0;
1149
								foreach($changed as &$r)
1150
								{
1151
									if ($r)
1152
									{
1153
										++$ok;
1154
									}
1155
									else
1156
									{
1157
										++$failed;
1158
									}
1159
								}
1160
								if ($ok && !$failed)
1161
								{
1162
									if(!$perm_changed++) $msg .= lang('Permissions of %1 changed.',$path.' '.lang('and all it\'s childeren'));
1163
									$content['old'][$name] = $content[$name];
1164
								}
1165
								elseif($failed)
1166
								{
1167
									if(!$perm_failed++) $msg .= lang('Failed to change permissions of %1!',$path.lang('and all it\'s childeren').
1168
										($ok ? ' ('.lang('%1 failed, %2 succeded',$failed,$ok).')' : ''));
1169
								}
1170
							}
1171
							elseif (call_user_func_array($cmd,array($path,$value)))
1172
							{
1173
								$msg .= lang('Permissions of %1 changed.',$path);
1174
								$content['old'][$name] = $content[$name];
1175
							}
1176
							else
1177
							{
1178
								$msg .= lang('Failed to change permissions of %1!',$path);
1179
							}
1180
						}
1181
					}
1182
				}
1183
				if ($props)
1184
				{
1185 View Code Duplication
					if (Vfs::proppatch($path,$props))
1186
					{
1187
						foreach($props as $prop)
1188
						{
1189
							$content['old'][$prop['name']] = $prop['val'];
1190
						}
1191
						$msg .= lang('Properties saved.');
1192
					}
1193
					else
1194
					{
1195
						$msg .= lang('Saving properties failed!');
1196
					}
1197
				}
1198
			}
1199
			elseif ($content['eacl'] && $content['is_owner'])
1200
			{
1201
				if ($content['eacl']['delete'])
1202
				{
1203
					list($ino_owner) = each($content['eacl']['delete']);
1204
					list(, $owner) = explode('-',$ino_owner,2);	// $owner is a group and starts with a minus!
1205
					$msg .= Vfs::eacl($path,null,$owner) ? lang('ACL deleted.') : lang('Error deleting the ACL entry!');
1206
				}
1207
				elseif ($button == 'eacl')
1208
				{
1209
					if (!$content['eacl_owner'])
1210
					{
1211
						$msg .= lang('You need to select an owner!');
1212
					}
1213
					else
1214
					{
1215
						$msg .= Vfs::eacl($path,$content['eacl']['rights'],$content['eacl_owner']) ?
1216
							lang('ACL added.') : lang('Error adding the ACL!');
1217
					}
1218
				}
1219
			}
1220
			Framework::refresh_opener($msg, 'filemanager', $refresh_path ? $refresh_path : $path, 'edit', null, '&path=[^&]*');
1221
			if ($button == 'save') Framework::window_close();
1222
		}
1223
		if ($content['is_link'] && !Vfs::stat($path))
1224
		{
1225
			$msg .= ($msg ? "\n" : '').lang('Link target %1 not found!',$content['symlink']);
1226
		}
1227
		$content['link'] = Egw::link(Vfs::download_url($path));
1228
		$content['icon'] = Vfs::mime_icon($content['mime']);
1229
		$content['msg'] = $msg;
1230
1231
		if (($readonlys['uid'] = !Vfs::$is_root) && !$content['uid']) $content['ro_uid_root'] = 'root';
1232
		// only owner can change group & perms
1233
		if (($readonlys['gid'] = !$content['is_owner'] ||
0 ignored issues
show
Comprehensibility introduced by
Consider adding parentheses for clarity. Current Interpretation: $readonlys['gid'] = (!$c...RL_SCHEME) == 'oldvfs'), Probably Intended Meaning: ($readonlys['gid'] = !$c...URL_SCHEME) == 'oldvfs'
Loading history...
1234
			Vfs::parse_url(Vfs::resolve_url($content['path']),PHP_URL_SCHEME) == 'oldvfs') ||// no uid, gid or perms in oldvfs
0 ignored issues
show
Bug introduced by
It seems like \EGroupware\Api\Vfs::res...e_url($content['path']) targeting EGroupware\Api\Vfs::resolve_url() can also be of type boolean; however, EGroupware\Api\Vfs::parse_url() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1235
				 !Vfs::is_writable($path))
1236
		{
1237
			if (!$content['gid']) $content['ro_gid_root'] = 'root';
1238
			foreach($content['perms'] as $name => $value)
0 ignored issues
show
Bug introduced by
The expression $content['perms'] of type string|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1239
			{
1240
				$readonlys['perms['.$name.']'] = true;
1241
			}
1242
		}
1243
		$readonlys['gid'] = $readonlys['gid'] || !Vfs::is_writable($path);
1244
		$readonlys['name'] = $path == '/' || !($dir = Vfs::dirname($path)) || !Vfs::is_writable($dir);
1245
		$readonlys['comment'] = !Vfs::is_writable($path);
1246
		$readonlys['tabs']['filemanager.file.preview'] = $readonlys['tabs']['filemanager.file.perms'] = $content['is_link'];
1247
1248
		// if neither owner nor is writable --> disable save&apply
1249
		$readonlys['button[save]'] = $readonlys['button[apply]'] = !$content['is_owner'] && !Vfs::is_writable($path);
1250
1251
		if (!($cfs = Api\Storage\Customfields::get('filemanager')))
1252
		{
1253
			$readonlys['tabs']['custom'] = true;
1254
		}
1255
		elseif (!Vfs::is_writable($path))
1256
		{
1257
			foreach($cfs as $name => $data)
1258
			{
1259
				$readonlys['#'.$name] = true;
1260
			}
1261
		}
1262
		$readonlys['tabs']['filemanager.file.eacl'] = true;	// eacl off by default
1263
		if ($content['is_dir'])
1264
		{
1265
			$readonlys['tabs']['filemanager.file.preview'] = true;	// no preview tab for dirs
1266
			$sel_options['rights']=$sel_options['owner']=$sel_options['group']=$sel_options['other'] = array(
1267
				7 => lang('Display and modification of content'),
1268
				5 => lang('Display of content'),
1269
				0 => lang('No access'),
1270
			);
1271
			if(($content['eacl'] = Vfs::get_eacl($content['path'])) !== false &&	// backend supports eacl
1272
				$GLOBALS['egw_info']['user']['account_id'] == Vfs::$user)	// leave eACL tab disabled for sharing
1273
			{
1274
				unset($readonlys['tabs']['filemanager.file.eacl']);	// --> switch the tab on again
1275
				foreach($content['eacl'] as &$eacl)
0 ignored issues
show
Bug introduced by
The expression $content['eacl'] of type array|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1276
				{
1277
					$eacl['path'] = rtrim(Vfs::parse_url($eacl['path'],PHP_URL_PATH),'/');
1278
					$readonlys['delete['.$eacl['ino'].'-'.$eacl['owner'].']'] = $eacl['ino'] != $content['ino'] ||
1279
						$eacl['path'] != $content['path'] || !$content['is_owner'];
1280
				}
1281
				array_unshift($content['eacl'],false);	// make the keys start with 1, not 0
1282
				$content['eacl']['owner'] = 0;
1283
				$content['eacl']['rights'] = 5;
1284
			}
1285
		}
1286
		else
1287
		{
1288
			$sel_options['owner']=$sel_options['group']=$sel_options['other'] = array(
1289
				6 => lang('Read & write access'),
1290
				4 => lang('Read access only'),
1291
				0 => lang('No access'),
1292
			);
1293
		}
1294
1295
		// mergeapp
1296
		$content['mergeapp'] = static::get_mergeapp($path, 'self');
0 ignored issues
show
Bug introduced by
Since get_mergeapp() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of get_mergeapp() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
1297
		$content['mergeapp_parent'] = static::get_mergeapp($path, 'parents');
0 ignored issues
show
Bug introduced by
Since get_mergeapp() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of get_mergeapp() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
1298
		if(!empty($content['mergeapp']))
1299
		{
1300
			$content['mergeapp_effective'] = $content['mergeapp'];
1301
		}
1302
		elseif(!empty($content['mergeapp_parent']))
1303
		{
1304
			$content['mergeapp_effective'] = $content['mergeapp_parent'];
1305
		}
1306
		else
1307
		{
1308
			$content['mergeapp_effective'] = null;
1309
		}
1310
		// mergeapp select options
1311
		$mergeapp_list = Link::app_list('merge');
1312
		unset($mergeapp_list[$GLOBALS['egw_info']['flags']['currentapp']]); // exclude filemanager from list
1313
		$mergeapp_empty = !empty($content['mergeapp_parent'])
1314
			? $mergeapp_list[$content['mergeapp_parent']] . ' (parent setting)' : '';
1315
		$sel_options['mergeapp'] = array(''	=> $mergeapp_empty);
1316
		$sel_options['mergeapp'] = $sel_options['mergeapp'] + $mergeapp_list;
1317
		// mergeapp other gui options
1318
		$content['mergeapp_itempicker_disabled'] = $content['is_dir'] || empty($content['mergeapp_effective']);
1319
1320
		$preserve = $content;
1321
		if (!isset($preserve['old']))
1322
		{
1323
			$preserve['old'] = array(
1324
				'perms' => $content['perms'],
1325
				'name'  => $content['name'],
1326
				'uid'   => $content['uid'],
1327
				'gid'   => $content['gid'],
1328
				'comment' => (string)$content['comment'],
1329
				'mergeapp' => $content['mergeapp']
1330
			);
1331
			if ($cfs) foreach($cfs as $name => $data)
1332
			{
1333
				$preserve['old']['#'.$name] = (string)$content['#'.$name];
1334
			}
1335
		}
1336
		if (Vfs::$is_root)
1337
		{
1338
			$tpl->setElementAttribute('sudouser', 'label', 'Logout');
1339
			$tpl->setElementAttribute('sudouser', 'help','Log out as superuser');
1340
			// Need a more complex submit because button type is buttononly, which doesn't submit
1341
			$tpl->setElementAttribute('sudouser', 'onclick','app.filemanager.set_sudoButton(widget,"login")');
1342
1343
		}
1344
		elseif ($button == 'sudo')
1345
		{
1346
			$tpl->setElementAttribute('sudouser', 'label', 'Superuser');
1347
			$tpl->setElementAttribute('sudouser', 'help','Enter setup user and password to get root rights');
1348
			$tpl->setElementAttribute('sudouser', 'onclick','app.filemanager.set_sudoButton(widget,"logout")');
1349
		}
1350
		else if (self::is_anonymous($GLOBALS['egw_info']['user']['account_id']))
1351
		{
1352
			// Just hide sudo for anonymous users
1353
			$readonlys['sudouser'] = true;
1354
		}
1355
		if (($extra_tabs = Vfs::getExtraInfo($path,$content)))
1356
		{
1357
			// add to existing tabs in template
1358
			$tpl->setElementAttribute('tabs', 'add_tabs', true);
1359
1360
			$tabs =& $tpl->getElementAttribute('tabs','tabs');
1361
			if (true) $tabs = array();
1362
1363
			foreach(isset($extra_tabs[0]) ? $extra_tabs : array($extra_tabs) as $extra_tab)
0 ignored issues
show
Bug introduced by
The expression isset($extra_tabs[0]) ? ...bs : array($extra_tabs) of type array|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1364
			{
1365
				$tabs[] = array(
1366
					'label' =>	$extra_tab['label'],
1367
					'template' =>	$extra_tab['name']
1368
				);
1369 View Code Duplication
				if ($extra_tab['data'] && is_array($extra_tab['data']))
1370
				{
1371
					$content = array_merge($content, $extra_tab['data']);
1372
				}
1373 View Code Duplication
				if ($extra_tab['preserve'] && is_array($extra_tab['preserve']))
1374
				{
1375
					$preserve = array_merge($preserve, $extra_tab['preserve']);
1376
				}
1377 View Code Duplication
				if ($extra_tab['readonlys'] && is_array($extra_tab['readonlys']))
1378
				{
1379
					$readonlys = array_merge($readonlys, $extra_tab['readonlys']);
1380
				}
1381
			}
1382
		}
1383
		Framework::window_focus();
1384
		$GLOBALS['egw_info']['flags']['app_header'] = lang('Preferences').' '.Vfs::decodePath($path);
1385
1386
		$tpl->exec('filemanager.filemanager_ui.file',$content,$sel_options,$readonlys,$preserve,2);
1387
	}
1388
1389
	/**
1390
	 * Check if the user is anonymous user
1391
	 * @param integer $account_id
1392
	 */
1393
	protected static function is_anonymous($account_id)
1394
	{
1395
		$acl = new Api\Acl($account_id);
1396
		$acl->read_repository();
1397
		return $acl->check('anonymous', 1, 'phpgwapi');
1398
	}
1399
1400
	/**
1401
	 * Run given action on given path(es) and return array/object with values for keys 'msg', 'errs', 'dirs', 'files'
1402
	 *
1403
	 * @param string $action eg. 'delete', ...
1404
	 * @param array $selected selected path(s)
1405
	 * @param string $dir=null current directory
1406
	 * @see static::action()
1407
	 */
1408
	public static function ajax_action($action, $selected, $dir=null, $props=null)
1409
	{
1410
		// do we have root rights, need to run here too, as method is static and therefore does NOT run __construct
1411
		if (Api\Cache::getSession('filemanager', 'is_root'))
1412
		{
1413
			Vfs::$is_root = true;
1414
		}
1415
		$response = Api\Json\Response::get();
1416
1417
		$arr = array(
1418
			'msg' => '',
1419
			'action' => $action,
1420
			'errs' => 0,
1421
			'dirs' => 0,
1422
			'files' => 0,
1423
		);
1424
1425
		if (!isset($dir)) $dir = array_pop($selected);
1426
1427
		switch($action)
1428
		{
1429
			case 'upload':
1430
				$script_error = 0;
1431
				foreach($selected as $tmp_name => &$data)
1432
				{
1433
					$path = Vfs::concat($dir, Vfs::encodePathComponent($data['name']));
0 ignored issues
show
Bug introduced by
It seems like \EGroupware\Api\Vfs::enc...omponent($data['name']) targeting EGroupware\Api\Vfs::encodePathComponent() can also be of type array; however, EGroupware\Api\Vfs::concat() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1434
1435
					if(Vfs::deny_script($path))
1436
					{
1437 View Code Duplication
						if (!isset($script_error))
1438
						{
1439
							$arr['msg'] .= ($arr['msg'] ? "\n" : '').lang('You are NOT allowed to upload a script!');
1440
						}
1441
						++$script_error;
1442
						++$arr['errs'];
1443
						unset($selected[$tmp_name]);
1444
					}
1445
					elseif (Vfs::is_dir($path))
1446
					{
1447
						$data['confirm'] = 'is_dir';
1448
					}
1449
					elseif (!$data['confirmed'] && Vfs::stat($path))
1450
					{
1451
						$data['confirm'] = true;
1452
					}
1453
					else
1454
					{
1455
						if (is_dir($GLOBALS['egw_info']['server']['temp_dir']) && is_writable($GLOBALS['egw_info']['server']['temp_dir']))
1456
						{
1457
							$tmp_path = $GLOBALS['egw_info']['server']['temp_dir'] . '/' . basename($tmp_name);
1458
						}
1459
						else
1460
						{
1461
							$tmp_path = ini_get('upload_tmp_dir').'/'.basename($tmp_name);
1462
						}
1463
1464
						if (Vfs::copy_uploaded($tmp_path, $path, $props, false))
1465
						{
1466
							++$arr['files'];
1467
							$uploaded[] = $data['name'];
1468
						}
1469
						else
1470
						{
1471
							++$arr['errs'];
1472
						}
1473
					}
1474
				}
1475 View Code Duplication
				if ($arr['errs'] > $script_error)
1476
				{
1477
					$arr['msg'] .= ($arr['msg'] ? "\n" : '').lang('Error uploading file!');
1478
				}
1479
				if ($arr['files'])
1480
				{
1481
					$arr['msg'] .= ($arr['msg'] ? "\n" : '').lang('%1 successful uploaded.', implode(', ', $uploaded));
1482
				}
1483
				$arr['uploaded'] = $selected;
1484
				$arr['path'] = $dir;
1485
				$arr['props'] = $props;
1486
				break;
1487
1488
			case 'sharelink':
1489
				$share = Vfs\Sharing::create($selected, Vfs\Sharing::READONLY, basename($selected), array() );
1490
				$arr["share_link"] = $link = Vfs\Sharing::share2link($share);
1491
				$arr["template"] = Api\Etemplate\Widget\Template::rel2url('/filemanager/templates/default/share_dialog.xet');
1492
				break;
1493
1494
			// Upload, then link
1495
			case 'link':
1496
				// First upload
1497
				$arr = static::ajax_action('upload', $selected, $dir, $props);
1498
				$app_dir = Link::vfs_path($props['entry']['app'],$props['entry']['id'],'',true);
1499
1500
				foreach($arr['uploaded'] as $file)
1501
				{
1502
					$target=Vfs::concat($dir,Vfs::encodePathComponent($file['name']));
0 ignored issues
show
Bug introduced by
It seems like \EGroupware\Api\Vfs::enc...omponent($file['name']) targeting EGroupware\Api\Vfs::encodePathComponent() can also be of type array; however, EGroupware\Api\Vfs::concat() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1503
					if (Vfs::file_exists($target) && $app_dir)
1504
					{
1505
						if (!Vfs::file_exists($app_dir)) Vfs::mkdir($app_dir);
1506
						error_log("Symlinking $target to $app_dir");
1507
						Vfs::symlink($target, Vfs::concat($app_dir,Vfs::encodePathComponent($file['name'])));
0 ignored issues
show
Bug introduced by
It seems like \EGroupware\Api\Vfs::enc...omponent($file['name']) targeting EGroupware\Api\Vfs::encodePathComponent() can also be of type array; however, EGroupware\Api\Vfs::concat() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1508
					}
1509
				}
1510
				// Must return to avoid adding to $response again
1511
				return;
1512
1513
			default:
1514
				$arr['msg'] = static::action($action, $selected, $dir, $arr['errs'], $arr['dirs'], $arr['files']);
1515
		}
1516
		$response->data($arr);
1517
		//error_log(__METHOD__."('$action',".array2string($selected).') returning '.array2string($arr));
1518
		return $arr;
1519
	}
1520
1521
	/**
1522
	 * Convert perms array back to integer mode
1523
	 *
1524
	 * @param array $perms with keys owner, group, other, executable, sticky
1525
	 * @return int
1526
	 */
1527
	private function perms2mode(array $perms)
1528
	{
1529
		$mode = $perms['owner'] << 6 | $perms['group'] << 3 | $perms['other'];
1530
		if ($mode['executable'])
1531
		{
1532
			$mode |= 0111;
1533
		}
1534
		if ($mode['sticky'])
1535
		{
1536
			$mode |= 0x201;
1537
		}
1538
		return $mode;
1539
	}
1540
}
1541