Completed
Push — 16.1 ( dce808...4651d6 )
by Nathan
68:40 queued 52:57
created

filemanager_ui::init_views()   B

Complexity

Conditions 5
Paths 2

Size

Total Lines 18
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 8
nc 2
nop 0
dl 0
loc 18
rs 8.8571
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
			),
210
			// DRAG and DROP events
211
			'file_drag' => array(
212
				'dragType' => array('file','link'),
213
				'type' => 'drag',
214
				'onExecute' => 'javaScript:app.filemanager.drag'
215
			),
216
			'file_drop_mail' => array(
217
				'type' => 'drop',
218
				'acceptedTypes' => 'mail',
219
				'onExecute' => 'javaScript:app.filemanager.drop',
220
				'hideOnDisabled' => true
221
			),
222
			'file_drop_move' => array(
223
				'icon' => 'stylite/move',
224
				'acceptedTypes' => 'file',
225
				'caption' => lang('Move into folder'),
226
				'type' => 'drop',
227
				'onExecute' => 'javaScript:app.filemanager.drop',
228
				'default' => true
229
			),
230
			'file_drop_copy' => array(
231
				'icon' => 'stylite/edit_copy',
232
				'acceptedTypes' => 'file',
233
				'caption' => lang('Copy into folder'),
234
				'type' => 'drop',
235
				'onExecute' => 'javaScript:app.filemanager.drop'
236
			),
237
			'file_drop_symlink' => array(
238
				'icon' => 'linkpaste',
239
				'acceptedTypes' => 'file',
240
				'caption' => lang('Link into folder'),
241
				'type' => 'drop',
242
				'onExecute' => 'javaScript:app.filemanager.drop'
243
			)
244
		);
245
		if (!isset($GLOBALS['egw_info']['user']['apps']['mail']))
246
		{
247
			unset($actions['mail']);
248
		}
249
		else
250
		{
251
			foreach(Vfs\Sharing::$modes as $mode => $data)
252
			{
253
				$actions['mail']['children']['mail_'.$mode] = array(
254
					'caption' => $data['label'],
255
					'hint' => $data['title'],
256
					'group' => 2,
257
					'onExecute' => 'javaScript:app.filemanager.mail',
258
				);
259
			}
260
		}
261
		// This would be done automatically, but we're overriding
262
		foreach($actions as $action_id => $action)
263
		{
264
			if($action['type'] == 'drop' && $action['caption'])
265
			{
266
				$action['type'] = 'popup';
267
				if($action['acceptedTypes'] == 'file')
268
				{
269
					$action['enabled'] = 'javaScript:app.filemanager.paste_enabled';
270
				}
271
				$actions['paste']['children']["{$action_id}_paste"] = $action;
272
			}
273
		}
274
275
		// Anonymous users have limited actions
276
		if(self::is_anonymous($GLOBALS['egw_info']['user']['account_id']))
277
		{
278
			self::restrict_anonymous_actions($actions);
279
		}
280
		return $actions;
281
	}
282
283
	/**
284
	 * Get mergeapp property for given path
285
	 *
286
	 * @param string $path
287
	 * @param string $scope (default) or 'parents'
288
	 *    $scope == 'self' query only the given path
289
	 *    $scope == 'parents' query only path parents for property (first parent in hierarchy upwards wins)
290
	 *
291
	 * @return string merge application or NULL if no property found
292
	 */
293
	private static function get_mergeapp($path, $scope='self')
294
	{
295
		$app = null;
296
		switch($scope)
297
		{
298
			case 'self':
299
				$props = Vfs::propfind($path, static::$merge_prop_namespace);
300
				$app = empty($props) ? null : $props[0]['val'];
301
				break;
302
			case 'parents':
303
				// search for props in parent directories
304
				$currentpath = $path;
305
				while($dir = Vfs::dirname($currentpath))
306
				{
307
					$props = Vfs::propfind($dir, static::$merge_prop_namespace);
308
					if(!empty($props))
309
					{
310
						// found prop in parent directory
311
						return $app = $props[0]['val'];
312
					}
313
					$currentpath = $dir;
314
				}
315
				break;
316
		}
317
318
		return $app;
319
	}
320
321
	/**
322
	 * Main filemanager page
323
	 *
324
	 * @param array $content
325
	 * @param string $msg
326
	 */
327
	function index(array $content=null,$msg=null)
328
	{
329
		if (!is_array($content))
330
		{
331
			$content = array(
332
				'nm' => Api\Cache::getSession('filemanager', 'index'),
333
			);
334
			if (!is_array($content['nm']))
335
			{
336
				$content['nm'] = array(
337
					'get_rows'       =>	'filemanager.filemanager_ui.get_rows',	// I  method/callback to request the data for the rows eg. 'notes.bo.get_rows'
338
					'filter'         => '',	// current dir only
339
					'no_filter2'     => True,	// I  disable the 2. filter (params are the same as for filter)
340
					'no_cat'         => True,	// I  disable the cat-selectbox
341
					'lettersearch'   => True,	// I  show a lettersearch
342
					'searchletter'   =>	false,	// I0 active letter of the lettersearch or false for [all]
343
					'start'          =>	0,		// IO position in list
344
					'order'          =>	'name',	// IO name of the column to sort after (optional for the sortheaders)
345
					'sort'           =>	'ASC',	// IO direction of the sort: 'ASC' or 'DESC'
346
					'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
347
					'csv_fields'     =>	false, // I  false=disable csv export, true or unset=enable it with auto-detected fieldnames,
348
									//or array with name=>label or name=>array('label'=>label,'type'=>type) pairs (type is a eT widget-type)
349
					'row_id'         => 'path',
350
					'row_modified'   => 'mtime',
351
					'parent_id'      => 'dir',
352
					'is_parent'      => 'is_dir',
353
					'favorites'      => true,
354
					'placeholder_actions' => array('mkdir','paste','file_drop_mail','file_drop_move','file_drop_copy','file_drop_symlink')
355
				);
356
				$content['nm']['path'] = static::get_home_dir();
357
			}
358
			$content['nm']['actions'] = static::get_actions();
359
			$content['nm']['home_dir'] = static::get_home_dir();
360
			$content['nm']['view'] = $GLOBALS['egw_info']['user']['preferences']['filemanager']['nm_view'];
361
362
			if (isset($_GET['msg'])) $msg = $_GET['msg'];
363
364
			// Blank favorite set via GET needs special handling for path
365
			if (isset($_GET['favorite']) && $_GET['favorite'] == 'blank')
366
			{
367
				$content['nm']['path'] = static::get_home_dir();
368
			}
369
			// switch to projectmanager folders
370
			if (isset($_GET['pm_id']))
371
			{
372
				$_GET['path'] = '/apps/projectmanager'.((int)$_GET['pm_id'] ? '/'.(int)$_GET['pm_id'] : '');
373
			}
374
			if (isset($_GET['path']) && ($path = $_GET['path']))
375
			{
376
				switch($path)
377
				{
378
					case '..':
379
						$path = Vfs::dirname($content['nm']['path']);
380
						break;
381
					case '~':
382
						$path = static::get_home_dir();
383
						break;
384
				}
385
				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...
386
				{
387
					$content['nm']['path'] = $path;
388
				}
389
				else
390
				{
391
					$msg .= lang('The requested path %1 is not available.', $path ? Vfs::decodePath($path) : "false");
392
				}
393
				// reset lettersearch as it confuses users (they think the dir is empty)
394
				$content['nm']['searchletter'] = false;
395
				// switch recusive display off
396
				if (!$content['nm']['filter']) $content['nm']['filter'] = '';
397
			}
398
		}
399
		$view = static::get_view();
400
401
		call_user_func($view,$content,$msg);
402
	}
403
404
	/**
405
	 * Make the current user (vfs) root
406
	 *
407
	 * The user/pw is either the setup config user or a specially configured vfs_root user
408
	 *
409
	 * @param string $user setup config user to become root or '' to log off as root
410
	 * @param string $password setup config password to become root
411
	 * @param boolean &$is_setup=null on return true if authenticated user is setup config user, false otherwise
412
	 * @return boolean true is root user given, false otherwise (including logout / empty $user)
413
	 */
414
	protected function sudo($user='',$password=null,&$is_setup=null)
415
	{
416
		if (!$user)
417
		{
418
			$is_root = $is_setup = false;
419
		}
420
		else
421
		{
422
			// config user & password
423
			$is_setup = Api\Session::user_pw_hash($user,$password) === $GLOBALS['egw_info']['server']['config_hash'];
424
			// or vfs root user from setup >> configuration
425
			$is_root = $is_setup ||	$GLOBALS['egw_info']['server']['vfs_root_user'] &&
426
				in_array($user,preg_split('/, */',$GLOBALS['egw_info']['server']['vfs_root_user'])) &&
427
				$GLOBALS['egw']->auth->authenticate($user, $password, 'text');
428
		}
429
		//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));
430
		Api\Cache::setSession('filemanager', 'is_setup',$is_setup);
431
		Api\Cache::setSession('filemanager', 'is_root',Vfs::$is_root = $is_root);
432
		return Vfs::$is_root;
433
	}
434
435
	/**
436
	 * Filemanager listview
437
	 *
438
	 * @param array $content
439
	 * @param string $msg
440
	 */
441
	function listview(array $content=null,$msg=null)
442
	{
443
		$tpl = new Etemplate('filemanager.index');
444
445
		if($msg) Framework::message($msg);
446
447
		if (($content['nm']['action'] || $content['nm']['rows']) && (empty($content['button']) || !isset($content['button'])))
448
		{
449
			if ($content['nm']['action'])
450
			{
451
				$msg = static::action($content['nm']['action'],$content['nm']['selected'],$content['nm']['path']);
452
				if($msg) Framework::message($msg);
453
454
				// clean up after action
455
				unset($content['nm']['selected']);
456
				// 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
457
				if (isset($content['nm']['action'])) unset($content['nm']['action']);
458
				if (isset($content['nm']['nm_action'])) unset($content['nm']['nm_action']);
459
				if (isset($content['nm_action'])) unset($content['nm_action']);
460
				// we dont use ['nm']['rows']['delete'], so unset it, if it is present
461
				if (isset($content['nm']['rows']['delete'])) unset($content['nm']['rows']['delete']);
462
			}
463
			elseif($content['nm']['rows']['delete'])
464
			{
465
				$msg = static::action('delete',array_keys($content['nm']['rows']['delete']),$content['nm']['path']);
466
				if($msg) Framework::message($msg);
467
468
				// clean up after action
469
				unset($content['nm']['rows']['delete']);
470
				// reset any occasion where action may be stored, as we use ['nm']['rows']['delete'] anyhow
471
				// we clean this up, as it may be ressurected out of the helpers by etemplate, which is quite unconvenient in case of action delete
472
				if (isset($content['nm']['action'])) unset($content['nm']['action']);
473
				if (isset($content['nm']['nm_action'])) unset($content['nm']['nm_action']);
474
				if (isset($content['nm_action'])) unset($content['nm_action']);
475
				if (isset($content['nm']['selected'])) unset($content['nm']['selected']);
476
			}
477
			unset($content['nm']['rows']);
478
			Api\Cache::setSession('filemanager', 'index',$content['nm']);
479
		}
480
481
		// be tolerant with (in previous versions) not correct urlencoded pathes
482
		if ($content['nm']['path'][0] == '/' && !Vfs::stat($content['nm']['path'],true) && Vfs::stat(urldecode($content['nm']['path'])))
483
		{
484
			$content['nm']['path'] = urldecode($content['nm']['path']);
485
		}
486
		if ($content['button'])
487
		{
488
			if ($content['button'])
489
			{
490
				list($button) = each($content['button']);
491
				unset($content['button']);
492
			}
493
			switch($button)
494
			{
495
				case 'upload':
496
					if (!$content['upload'])
497
					{
498
						Framework::message(lang('You need to select some files first!'),'error');
499
						break;
500
					}
501
					$upload_success = $upload_failure = array();
502
					foreach(isset($content['upload'][0]) ? $content['upload'] : array($content['upload']) as $upload)
503
					{
504
						// encode chars which special meaning in url/vfs (some like / get removed!)
505
						$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...
506
						if ($upload &&
507
							(Vfs::is_writable($content['nm']['path']) || Vfs::is_writable($to)) &&
508
							copy($upload['tmp_name'],Vfs::PREFIX.$to))
509
						{
510
							$upload_success[] = $upload['name'];
511
						}
512
						else
513
						{
514
							$upload_failure[] = $upload['name'];
515
						}
516
					}
517
					$content['nm']['msg'] = '';
518
					if ($upload_success)
519
					{
520
						Framework::message( count($upload_success) == 1 && !$upload_failure ? lang('File successful uploaded.') :
521
							lang('%1 successful uploaded.',implode(', ',$upload_success)));
522
					}
523
					if ($upload_failure)
524
					{
525
						Framework::message(lang('Error uploading file!')."\n".etemplate::max_upload_size_message(),'error');
526
					}
527
					break;
528
			}
529
		}
530
		$readonlys['button[mailpaste]'] = !isset($GLOBALS['egw_info']['user']['apps']['mail']);
531
532
		$sel_options['filter'] = array(
533
			'' => 'Current directory',
534
			'2' => 'Directories sorted in',
535
			'3' => 'Show hidden files',
536
			'4' => 'All subdirectories',
537
			'5' => 'Files from links',
538
			'0'  => 'Files from subdirectories',
539
		);
540
		// sharing has no divAppbox, we need to set popupMainDiv instead, to be able to drop files everywhere
541
		if (substr($_SERVER['SCRIPT_FILENAME'], -10) == '/share.php')
542
		{
543
			$tpl->setElementAttribute('nm[buttons][upload]', 'drop_target', 'popupMainDiv');
544
		}
545
		// Set view button to match current settings
546
		if($content['nm']['view'] == 'tile')
547
		{
548
			$tpl->setElementAttribute('nm[button][change_view]','statustext',lang('List view'));
549
			$tpl->setElementAttribute('nm[button][change_view]','image','list_row');
550
		}
551
		// if initial load is done via GET request (idots template or share.php)
552
		// get_rows cant call app.filemanager.set_readonly, so we need to do that here
553
		$content['initial_path_readonly'] = !Vfs::is_writable($content['nm']['path']);
554
555
		$tpl->exec('filemanager.filemanager_ui.index',$content,$sel_options,$readonlys,array('nm' => $content['nm']));
556
	}
557
558
	/**
559
	 * Get the configured start directory for the current user
560
	 *
561
	 * @return string
562
	 */
563
	static function get_home_dir()
564
	{
565
		$start = '/home/'.$GLOBALS['egw_info']['user']['account_lid'];
566
567
		// check if user specified a valid startpath in his prefs --> use it
568
		if (($path = $GLOBALS['egw_info']['user']['preferences']['filemanager']['startfolder']) &&
569
			$path[0] == '/' && Vfs::is_dir($path) && Vfs::check_access($path, Vfs::READABLE))
570
		{
571
			$start = $path;
572
		}
573
		elseif (!Vfs::is_dir($start) && Vfs::check_access($start, Vfs::READABLE))
574
		{
575
			$start = '/';
576
		}
577
		return $start;
578
	}
579
580
	/**
581
	 * Run a certain action with the selected file
582
	 *
583
	 * @param string $action
584
	 * @param array $selected selected pathes
585
	 * @param mixed $dir current directory
586
	 * @param int &$errs=null on return number of errors
587
	 * @param int &$dirs=null on return number of dirs deleted
588
	 * @param int &$files=null on return number of files deleted
589
	 * @return string success or failure message displayed to the user
590
	 */
591
	static public function action($action,$selected,$dir=null,&$errs=null,&$files=null,&$dirs=null)
592
	{
593
		if (!count($selected))
594
		{
595
			return lang('You need to select some files first!');
596
		}
597
		$errs = $dirs = $files = 0;
598
599
		switch($action)
600
		{
601
602
			case 'delete':
603
				return static::do_delete($selected,$errs,$files,$dirs);
604
605
			case 'mail':
606
			case 'copy':
607
				foreach($selected as $path)
608
				{
609
					if (strpos($path, 'mail::') === 0 && $path = substr($path, 6))
610
					{
611
						// Support for dropping mail in filemanager - Pass mail back to mail app
612
						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...
613
						{
614
							++$files;
615
						}
616
						else
617
						{
618
							++$errs;
619
						}
620
					}
621
					elseif (!Vfs::is_dir($path))
622
					{
623
						$to = Vfs::concat($dir,Vfs::basename($path));
624
						if ($path != $to && Vfs::copy($path,$to))
625
						{
626
							++$files;
627
						}
628
						else
629
						{
630
							++$errs;
631
						}
632
					}
633
					else
634
					{
635
						$len = strlen(dirname($path));
636
						foreach(Vfs::find($path) as $p)
637
						{
638
							$to = $dir.substr($p,$len);
639
							if ($to == $p)	// cant copy into itself!
640
							{
641
								++$errs;
642
								continue;
643
							}
644
							if (($is_dir = Vfs::is_dir($p)) && Vfs::mkdir($to,null,STREAM_MKDIR_RECURSIVE))
645
							{
646
								++$dirs;
647
							}
648
							elseif(!$is_dir && Vfs::copy($p,$to))
649
							{
650
								++$files;
651
							}
652
							else
653
							{
654
								++$errs;
655
							}
656
						}
657
					}
658
				}
659
				if ($errs)
660
				{
661
					return lang('%1 errors copying (%2 diretories and %3 files copied)!',$errs,$dirs,$files);
662
				}
663
				return $dirs ? lang('%1 directories and %2 files copied.',$dirs,$files) : lang('%1 files copied.',$files);
664
665
			case 'move':
666
				foreach($selected as $path)
667
				{
668
					$to = Vfs::is_dir($dir) || count($selected) > 1 ? Vfs::concat($dir,Vfs::basename($path)) : $dir;
669
					if ($path != $to && Vfs::rename($path,$to))
670
					{
671
						++$files;
672
					}
673
					else
674
					{
675
						++$errs;
676
					}
677
				}
678
				if ($errs)
679
				{
680
					return lang('%1 errors moving (%2 files moved)!',$errs,$files);
681
				}
682
				return lang('%1 files moved.',$files);
683
684
			case 'symlink':	// symlink given files to $dir
685
				foreach((array)$selected as $target)
686
				{
687
					$link = Vfs::concat($dir, Vfs::basename($target));
688
					if (!Vfs::stat($dir) || ($ok = Vfs::mkdir($dir,0,true)))
689
					{
690
						if(!$ok)
691
						{
692
							$errs++;
693
							continue;
694
						}
695
					}
696
					if ($target[0] != '/') $target = Vfs::concat($dir, $target);
697
					if (!Vfs::stat($target))
698
					{
699
						return lang('Link target %1 not found!', Vfs::decodePath($target));
700
					}
701
					if ($target != $link && Vfs::symlink($target, $link))
702
					{
703
						++$files;
704
					}
705
					else
706
					{
707
						++$errs;
708
					}
709
				}
710
				if (count((array)$selected) == 1)
711
				{
712
					return $files ? lang('Symlink to %1 created.', Vfs::decodePath($target)) :
713
						lang('Error creating symlink to target %1!', Vfs::decodePath($target));
714
				}
715
				$ret = lang('%1 elements linked.', $files);
716
				if ($errs)
717
				{
718
					$ret = lang('%1 errors linking (%2)!',$errs, $ret);
719
				}
720
				return $ret;//." Vfs::symlink('$target', '$link')";
721
722
			case 'createdir':
723
				$dst = Vfs::concat($dir, is_array($selected) ? $selected[0] : $selected);
724
				if (Vfs::mkdir($dst, null, STREAM_MKDIR_RECURSIVE))
725
				{
726
					return lang("Directory successfully created.");
727
				}
728
				return lang("Error while creating directory.");
729
730
			case 'saveaszip':
731
				Vfs::download_zip($selected);
732
				exit;
733
734
			default:
735
				list($action, $settings) = explode('_', $action, 2);
736
				switch($action)
737
				{
738 View Code Duplication
					case 'document':
739
						if (!$settings) $settings = $GLOBALS['egw_info']['user']['preferences']['filemanager']['default_document'];
740
						$document_merge = new filemanager_merge(Vfs::decodePath($dir));
741
						$msg = $document_merge->download($settings, $selected, '', $GLOBALS['egw_info']['user']['preferences']['filemanager']['document_dir']);
742
						if($msg) return $msg;
743
						$errs = count($selected);
744
						return false;
745
				}
746
		}
747
		return "Unknown action '$action'!";
748
	}
749
750
	/**
751
	 * Delete selected files and return success or error message
752
	 *
753
	 * @param array $selected
754
	 * @param int &$errs=null on return number of errors
755
	 * @param int &$dirs=null on return number of dirs deleted
756
	 * @param int &$files=null on return number of files deleted
757
	 * @return string
758
	 */
759
	public static function do_delete(array $selected, &$errs=null, &$dirs=null, &$files=null)
760
	{
761
		$dirs = $files = $errs = 0;
762
		// we first delete all selected links (and files)
763
		// feeding the links to dirs to Vfs::find() deletes the content of the dirs, not just the link!
764
		foreach($selected as $key => $path)
765
		{
766
			if (!Vfs::is_dir($path) || Vfs::is_link($path))
767
			{
768
				if (Vfs::unlink($path))
769
				{
770
					++$files;
771
				}
772
				else
773
				{
774
					++$errs;
775
				}
776
				unset($selected[$key]);
777
			}
778
		}
779
		if ($selected)	// somethings left to delete
780
		{
781
			// some precaution to never allow to (recursivly) remove /, /apps or /home
782
			foreach((array)$selected as $path)
783
			{
784
				if (Vfs::isProtectedDir($path))
785
				{
786
					$errs++;
787
					return lang("Cautiously rejecting to remove folder '%1'!",Vfs::decodePath($path));
788
				}
789
			}
790
			// now we use find to loop through all files and dirs: (selected only contains dirs now)
791
			// - depth=true to get first the files and then the dir containing it
792
			// - hidden=true to also return hidden files (eg. Thumbs.db), as we cant delete non-empty dirs
793
			// - show-deleted=false to not (finally) deleted versioned files
794
			foreach(Vfs::find($selected,array('depth'=>true,'hidden'=>true,'show-deleted'=>false)) as $path)
795
			{
796
				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...
797
				{
798
					++$dirs;
799
				}
800
				elseif (!$is_dir && Vfs::unlink($path))
801
				{
802
					++$files;
803
				}
804
				else
805
				{
806
					++$errs;
807
				}
808
			}
809
		}
810
		if ($errs)
811
		{
812
			return lang('%1 errors deleteting (%2 directories and %3 files deleted)!',$errs,$dirs,$files);
813
		}
814
		if ($dirs)
815
		{
816
			return lang('%1 directories and %2 files deleted.',$dirs,$files);
817
		}
818
		return $files == 1 ? lang('File deleted.') : lang('%1 files deleted.',$files);
819
	}
820
821
	/**
822
	 * Callback to fetch the rows for the nextmatch widget
823
	 *
824
	 * @param array $query
825
	 * @param array &$rows
826
	 */
827
	function get_rows(&$query, &$rows)
828
	{
829
		// do NOT store query, if hierarchical data / children are requested
830 View Code Duplication
		if (!$query['csv_export'])
831
		{
832
			Api\Cache::setSession('filemanager', 'index',
833
				array_diff_key ($query, array_flip(array('rows','actions','action_links','placeholder_actions'))));
834
		}
835
		if(!$query['path']) $query['path'] = static::get_home_dir();
836
837
		// Change template to match selected view
838
		if($query['view'])
839
		{
840
			$query['template'] = ($query['view'] == 'row' ? 'filemanager.index.rows' : 'filemanager.tile');
841
842
			// Store as preference but only for index, not home
843
			if($query['get_rows'] == 'filemanager.filemanager_ui.get_rows')
844
			{
845
				$GLOBALS['egw']->preferences->add('filemanager','nm_view',$query['view']);
846
				$GLOBALS['egw']->preferences->save_repository();
847
			}
848
		}
849
		// be tolerant with (in previous versions) not correct urlencoded pathes
850
		if (!Vfs::stat($query['path'],true) && Vfs::stat(urldecode($query['path'])))
851
		{
852
			$query['path'] = urldecode($query['path']);
853
		}
854
		if (!Vfs::stat($query['path'],true) || !Vfs::is_dir($query['path']) || !Vfs::check_access($query['path'],Vfs::READABLE))
855
		{
856
			// only redirect, if it would be to some other location, gives redirect-loop otherwise
857
			if ($query['path'] != ($path = static::get_home_dir()))
858
			{
859
				// we will leave here, since we are not allowed, or the location does not exist. Index must handle that, and give
860
				// an appropriate message
861
				Egw::redirect_link('/index.php',array('menuaction'=>'filemanager.filemanager_ui.index',
862
					'path' => $path,
863
					'msg' => lang('The requested path %1 is not available.',Vfs::decodePath($query['path'])),
864
					'ajax' => 'true'
865
				));
866
			}
867
			$rows = array();
868
			return 0;
869
		}
870
		$rows = $dir_is_writable = array();
871
		if($query['searchletter'] && !empty($query['search']))
872
		{
873
			$namefilter = '/^'.$query['searchletter'].'.*'.str_replace(array('\\?','\\*'),array('.{1}','.*'),preg_quote($query['search'])).'/i';
874
			if ($query['searchletter'] == strtolower($query['search'][0]))
875
			{
876
				$namefilter = '/^('.$query['searchletter'].'.*'.str_replace(array('\\?','\\*'),array('.{1}','.*'),preg_quote($query['search'])).'|'.
877
					str_replace(array('\\?','\\*'),array('.{1}','.*'),preg_quote($query['search'])).')/i';
878
			}
879
		}
880
		elseif ($query['searchletter'])
881
		{
882
			$namefilter = '/^'.$query['searchletter'].'/i';
883
		}
884
		elseif(!empty($query['search']))
885
		{
886
			$namefilter = '/'.str_replace(array('\\?','\\*'),array('.{1}','.*'),preg_quote($query['search'])).'/i';
887
		}
888
889
		// Re-map so 'No filters' favorite ('') is depth 1
890
		$filter = $query['filter'] === '' ? 1 : $query['filter'];
891
892
		$maxdepth = $filter && $filter != 4 ? (int)(boolean)$filter : null;
893
		if($filter == 5) $maxdepth = 2;
894
		$n = 0;
895
		$vfs_options = array(
896
			'mindepth' => 1,
897
			'maxdepth' => $maxdepth,
898
			'dirsontop' => $filter <= 1,
899
			'type' => $filter && $filter != 5 ? ($filter == 4 ? 'd' : null) : ($filter == 5 ? 'F':'f'),
900
			'order' => $query['order'], 'sort' => $query['sort'],
901
			'limit' => (int)$query['num_rows'].','.(int)$query['start'],
902
			'need_mime' => true,
903
			'name_preg' => $namefilter,
904
			'hidden' => $filter == 3,
905
			'follow' => $filter == 5,
906
		);
907
		if($query['col_filter']['mime'])
908
		{
909
			$vfs_options['mime'] = $query['col_filter']['mime'];
910
		}
911
		foreach(Vfs::find(!empty($query['col_filter']['dir']) ? $query['col_filter']['dir'] : $query['path'],$vfs_options,true) as $path => $row)
912
		{
913
			//echo $path; _debug_array($row);
914
915
			$dir = dirname($path);
916
			if (!isset($dir_is_writable[$dir]))
917
			{
918
				$dir_is_writable[$dir] = Vfs::is_writable($dir);
919
			}
920
			if (Vfs::is_dir($path))
921
			{
922
				if (!isset($dir_is_writable[$path]))
923
				{
924
					$dir_is_writable[$path] = Vfs::is_writable($path);
925
				}
926
927
				$row['class'] .= 'isDir ';
928
				$row['is_dir'] = 1;
929
			}
930
			if(!$dir_is_writable[$path])
931
			{
932
				$row['class'] .= 'noEdit ';
933
			}
934
			$row['download_url'] = Vfs::download_url($path);
935
			$row['gid'] = -abs($row['gid']);	// gid are positive, but we use negagive account_id for groups internal
936
937
			$rows[++$n] = $row;
938
			$path2n[$path] = $n;
939
		}
940
		// query comments and cf's for the displayed rows
941
		$cols_to_show = explode(',',$GLOBALS['egw_info']['user']['preferences']['filemanager']['nextmatch-filemanager.index.rows']);
942
943
		// Always include comment in tiles
944
		if($query['view'] == 'tile')
945
		{
946
			$cols_to_show[] = 'comment';
947
		}
948
		$all_cfs = in_array('customfields',$cols_to_show) && $cols_to_show[count($cols_to_show)-1][0] != '#';
949
		if ($path2n && (in_array('comment',$cols_to_show) || in_array('customfields',$cols_to_show)) &&
950
			($path2props = Vfs::propfind(array_keys($path2n))))
951
		{
952
			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...
953
			{
954
				unset($row);	// fixes a weird problem with php5.1, does NOT happen with php5.2
955
				$row =& $rows[$path2n[$path]];
956
				if ( !is_array($props) ) continue;
957
				foreach($props as $prop)
958
				{
959
					if (!$all_cfs && $prop['name'][0] == '#' && !in_array($prop['name'],$cols_to_show)) continue;
960
					$row[$prop['name']] = strlen($prop['val']) < 64 ? $prop['val'] : substr($prop['val'],0,64).' ...';
961
				}
962
			}
963
		}
964
		// tell client-side if directory is writeable or not
965
		$response = Api\Json\Response::get();
966
		$response->call('app.filemanager.set_readonly', $query['path'], !Vfs::is_writable($query['path']));
967
968
		//_debug_array($readonlys);
969
		if ($GLOBALS['egw_info']['flags']['currentapp'] == 'projectmanager')
970
		{
971
			// we need our app.css file
972 View Code Duplication
			if (!file_exists(EGW_SERVER_ROOT.($css_file='/filemanager/templates/'.$GLOBALS['egw_info']['server']['template_set'].'/app.css')))
973
			{
974
				$css_file = '/filemanager/templates/default/app.css';
975
			}
976
			$GLOBALS['egw_info']['flags']['css'] .= "\n\t\t</style>\n\t\t".'<link href="'.$GLOBALS['egw_info']['server']['webserver_url'].
977
				$css_file.'?'.filemtime(EGW_SERVER_ROOT.$css_file).'" type="text/css" rel="StyleSheet" />'."\n\t\t<style>\n\t\t\t";
978
		}
979
		return Vfs::$find_total;
980
	}
981
982
	/**
983
	 * Preferences of a file/directory
984
	 *
985
	 * @param array $content
986
	 * @param string $msg
987
	 */
988
	function file(array $content=null,$msg='')
989
	{
990
		$tpl = new Etemplate('filemanager.file');
991
992
		if (!is_array($content))
993
		{
994
			if (isset($_GET['msg']))
995
			{
996
				$msg .= $_GET['msg'];
997
			}
998
			if (!($path = str_replace(array('#','?'),array('%23','%3F'),$_GET['path'])) ||	// ?, # need to stay encoded!
999
				// actions enclose pathes containing comma with "
1000
				($path[0] == '"' && substr($path,-1) == '"' && !($path = substr(str_replace('""','"',$path),1,-1))) ||
1001
				!($stat = Vfs::lstat($path)))
1002
			{
1003
				$msg .= lang('File or directory not found!')." path='$path', stat=".array2string($stat);
1004
			}
1005
			else
1006
			{
1007
				$content = $stat;
1008
				$content['name'] = $content['itempicker_merge']['name'] = Vfs::basename($path);
1009
				$content['dir'] = $content['itempicker_merge']['dir'] = ($dir = Vfs::dirname($path)) ? Vfs::decodePath($dir) : '';
1010
				$content['path'] = $path;
1011
				$content['hsize'] = Vfs::hsize($stat['size']);
1012
				$content['mime'] = Vfs::mime_content_type($path);
1013
				$content['gid'] *= -1;	// our widgets use negative gid's
1014 View Code Duplication
				if (($props = Vfs::propfind($path)))
1015
				{
1016
					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...
1017
					{
1018
						$content[$prop['name']] = $prop['val'];
1019
					}
1020
				}
1021
				if (($content['is_link'] = Vfs::is_link($path)))
1022
				{
1023
					$content['symlink'] = Vfs::readlink($path);
1024
				}
1025
			}
1026
			$content['tabs'] = $_GET['tabs'];
1027
			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...
1028
			{
1029
				$content['perms']['executable'] = (int)!!($content['mode'] & 0111);
1030
				$mask = 6;
1031
				if (preg_match('/^text/',$content['mime']) && $content['size'] < 100000)
1032
				{
1033
					$content['text_content'] = file_get_contents(Vfs::PREFIX.$path);
1034
				}
1035
			}
1036
			else
1037
			{
1038
				//currently not implemented in backend $content['perms']['sticky'] = (int)!!($content['mode'] & 0x201);
1039
				$mask = 7;
1040
			}
1041
			foreach(array('owner' => 6,'group' => 3,'other' => 0) as $name => $shift)
1042
			{
1043
				$content['perms'][$name] = ($content['mode'] >> $shift) & $mask;
1044
			}
1045
			$content['is_owner'] = Vfs::has_owner_rights($path,$content);
1046
		}
1047
		else
1048
		{
1049
			//_debug_array($content);
1050
			$path =& $content['path'];
1051
1052
			list($button) = @each($content['button']); unset($content['button']);
1053
			if(!$button && $content['sudo'])
1054
			{
1055
				// Button to stop sudo is not in button namespace
1056
				$button = 'sudo';
1057
				unset($content['sudo']);
1058
			}
1059
			// need to check 'setup' button (submit button in sudo popup), as some browsers (eg. chrome) also fill the hidden field
1060
			if ($button == 'sudo' && Vfs::$is_root || $button == 'setup' && $content['sudo']['user'])
1061
			{
1062
				$msg = $this->sudo($button == 'setup' ? $content['sudo']['user'] : '',$content['sudo']['passwd']) ?
1063
					lang('Root access granted.') : ($button == 'setup' && $content['sudo']['user'] ?
1064
					lang('Wrong username or password!') : lang('Root access stopped.'));
1065
				unset($content['sudo']);
1066
				$content['is_owner'] = Vfs::has_owner_rights($path);
1067
			}
1068
			if (in_array($button,array('save','apply')))
1069
			{
1070
				$props = array();
1071
				$perm_changed = $perm_failed = 0;
1072
				foreach($content['old'] as $name => $old_value)
1073
				{
1074
					if (isset($content[$name]) && ($old_value != $content[$name] ||
1075
						// do not check for modification, if modify_subs is checked!
1076
						$content['modify_subs'] && in_array($name,array('uid','gid','perms'))) &&
1077
						($name != 'uid' || Vfs::$is_root))
1078
					{
1079
						if ($name == 'name')
1080
						{
1081
							if (!($dir = Vfs::dirname($path)))
1082
							{
1083
								$msg .= lang('File or directory not found!')." Vfs::dirname('$path')===false";
1084
								if ($button == 'save') $button = 'apply';
1085
								continue;
1086
							}
1087
							$to = Vfs::concat($dir, $content['name']);
1088
							if (file_exists(Vfs::PREFIX.$to) && $content['confirm_overwrite'] !== $to)
1089
							{
1090
								$tpl->set_validation_error('name',lang("There's already a file with that name!").'<br />'.
1091
									lang('To overwrite the existing file store again.',lang($button)));
1092
								$content['confirm_overwrite'] = $to;
1093
								if ($button == 'save') $button = 'apply';
1094
								continue;
1095
							}
1096
							if (Vfs::rename($path,$to))
1097
							{
1098
								$msg .= lang('Renamed %1 to %2.',Vfs::decodePath(basename($path)),Vfs::decodePath(basename($to))).' ';
1099
								$content['old']['name'] = $content[$name];
1100
								$path = $to;
1101
								$content['mime'] = Api\MimeMagic::filename2mime($path);	// recheck mime type
1102
								$refresh_path = Vfs::dirname($path);	// for renames, we have to refresh the parent
1103
							}
1104
							else
1105
							{
1106
								$msg .= lang('Rename of %1 to %2 failed!',Vfs::decodePath(basename($path)),Vfs::decodePath(basename($to))).' ';
1107
								if (Vfs::deny_script($to))
1108
								{
1109
									$msg .= lang('You are NOT allowed to upload a script!').' ';
1110
								}
1111
							}
1112
						}
1113
						elseif ($name[0] == '#' || $name == 'comment')
1114
						{
1115
							$props[] = array('name' => $name, 'val' => $content[$name] ? $content[$name] : null);
1116
						}
1117
						elseif ($name == 'mergeapp')
1118
						{
1119
							$mergeprop = array(
1120
								array(
1121
									'ns'	=> static::$merge_prop_namespace,
1122
									'name'	=> 'mergeapp',
1123
									'val'	=> (!empty($content[$name]) ? $content[$name] : null),
1124
								),
1125
							);
1126 View Code Duplication
							if (Vfs::proppatch($path,$mergeprop))
1127
							{
1128
								$content['old'][$name] = $content[$name];
1129
								$msg .= lang('Setting for document merge saved.');
1130
							}
1131
							else
1132
							{
1133
								$msg .= lang('Saving setting for document merge failed!');
1134
							}
1135
						}
1136
						else
1137
						{
1138
							static $name2cmd = array('uid' => 'chown','gid' => 'chgrp','perms' => 'chmod');
1139
							$cmd = array('EGroupware\\Api\\Vfs',$name2cmd[$name]);
1140
							$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...
1141
							if ($content['modify_subs'])
1142
							{
1143
								if ($name == 'perms')
1144
								{
1145
									$changed = Vfs::find($path,array('type'=>'d'),$cmd,array($value));
1146
									$changed += Vfs::find($path,array('type'=>'f'),$cmd,array($value & 0666));	// no execute for files
1147
								}
1148
								else
1149
								{
1150
									$changed = Vfs::find($path,null,$cmd,array($value));
1151
								}
1152
								$ok = $failed = 0;
1153
								foreach($changed as &$r)
1154
								{
1155
									if ($r)
1156
									{
1157
										++$ok;
1158
									}
1159
									else
1160
									{
1161
										++$failed;
1162
									}
1163
								}
1164
								if ($ok && !$failed)
1165
								{
1166
									if(!$perm_changed++) $msg .= lang('Permissions of %1 changed.',$path.' '.lang('and all it\'s childeren'));
1167
									$content['old'][$name] = $content[$name];
1168
								}
1169
								elseif($failed)
1170
								{
1171
									if(!$perm_failed++) $msg .= lang('Failed to change permissions of %1!',$path.lang('and all it\'s childeren').
1172
										($ok ? ' ('.lang('%1 failed, %2 succeded',$failed,$ok).')' : ''));
1173
								}
1174
							}
1175
							elseif (call_user_func_array($cmd,array($path,$value)))
1176
							{
1177
								$msg .= lang('Permissions of %1 changed.',$path);
1178
								$content['old'][$name] = $content[$name];
1179
							}
1180
							else
1181
							{
1182
								$msg .= lang('Failed to change permissions of %1!',$path);
1183
							}
1184
						}
1185
					}
1186
				}
1187
				if ($props)
1188
				{
1189 View Code Duplication
					if (Vfs::proppatch($path,$props))
1190
					{
1191
						foreach($props as $prop)
1192
						{
1193
							$content['old'][$prop['name']] = $prop['val'];
1194
						}
1195
						$msg .= lang('Properties saved.');
1196
					}
1197
					else
1198
					{
1199
						$msg .= lang('Saving properties failed!');
1200
					}
1201
				}
1202
			}
1203
			elseif ($content['eacl'] && $content['is_owner'])
1204
			{
1205
				if ($content['eacl']['delete'])
1206
				{
1207
					list($ino_owner) = each($content['eacl']['delete']);
1208
					list(, $owner) = explode('-',$ino_owner,2);	// $owner is a group and starts with a minus!
1209
					$msg .= Vfs::eacl($path,null,$owner) ? lang('ACL deleted.') : lang('Error deleting the ACL entry!');
1210
				}
1211
				elseif ($button == 'eacl')
1212
				{
1213
					if (!$content['eacl_owner'])
1214
					{
1215
						$msg .= lang('You need to select an owner!');
1216
					}
1217
					else
1218
					{
1219
						$msg .= Vfs::eacl($path,$content['eacl']['rights'],$content['eacl_owner']) ?
1220
							lang('ACL added.') : lang('Error adding the ACL!');
1221
					}
1222
				}
1223
			}
1224
			Framework::refresh_opener($msg, 'filemanager', $refresh_path ? $refresh_path : $path, 'edit', null, '&path=[^&]*');
1225
			if ($button == 'save') Framework::window_close();
1226
		}
1227
		if ($content['is_link'] && !Vfs::stat($path))
1228
		{
1229
			$msg .= ($msg ? "\n" : '').lang('Link target %1 not found!',$content['symlink']);
1230
		}
1231
		$content['link'] = Egw::link(Vfs::download_url($path));
1232
		$content['icon'] = Vfs::mime_icon($content['mime']);
1233
		$content['msg'] = $msg;
1234
1235
		if (($readonlys['uid'] = !Vfs::$is_root) && !$content['uid']) $content['ro_uid_root'] = 'root';
1236
		// only owner can change group & perms
1237
		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...
1238
			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...
1239
				 !Vfs::is_writable($path))
1240
		{
1241
			if (!$content['gid']) $content['ro_gid_root'] = 'root';
1242
			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...
1243
			{
1244
				$readonlys['perms['.$name.']'] = true;
1245
			}
1246
		}
1247
		$readonlys['gid'] = $readonlys['gid'] || !Vfs::is_writable($path);
1248
		$readonlys['name'] = $path == '/' || !($dir = Vfs::dirname($path)) || !Vfs::is_writable($dir);
1249
		$readonlys['comment'] = !Vfs::is_writable($path);
1250
		$readonlys['tabs']['filemanager.file.preview'] = $readonlys['tabs']['filemanager.file.perms'] = $content['is_link'];
1251
1252
		// if neither owner nor is writable --> disable save&apply
1253
		$readonlys['button[save]'] = $readonlys['button[apply]'] = !$content['is_owner'] && !Vfs::is_writable($path);
1254
1255
		if (!($cfs = Api\Storage\Customfields::get('filemanager')))
1256
		{
1257
			$readonlys['tabs']['custom'] = true;
1258
		}
1259
		elseif (!Vfs::is_writable($path))
1260
		{
1261
			foreach($cfs as $name => $data)
1262
			{
1263
				$readonlys['#'.$name] = true;
1264
			}
1265
		}
1266
		$readonlys['tabs']['filemanager.file.eacl'] = true;	// eacl off by default
1267
		if ($content['is_dir'])
1268
		{
1269
			$readonlys['tabs']['filemanager.file.preview'] = true;	// no preview tab for dirs
1270
			$sel_options['rights']=$sel_options['owner']=$sel_options['group']=$sel_options['other'] = array(
1271
				7 => lang('Display and modification of content'),
1272
				5 => lang('Display of content'),
1273
				0 => lang('No access'),
1274
			);
1275
			if(($content['eacl'] = Vfs::get_eacl($content['path'])) !== false)	// backend supports eacl
1276
			{
1277
				unset($readonlys['tabs']['filemanager.file.eacl']);	// --> switch the tab on again
1278
				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...
1279
				{
1280
					$eacl['path'] = rtrim(Vfs::parse_url($eacl['path'],PHP_URL_PATH),'/');
1281
					$readonlys['delete['.$eacl['ino'].'-'.$eacl['owner'].']'] = $eacl['ino'] != $content['ino'] ||
1282
						$eacl['path'] != $content['path'] || !$content['is_owner'];
1283
				}
1284
				array_unshift($content['eacl'],false);	// make the keys start with 1, not 0
1285
				$content['eacl']['owner'] = 0;
1286
				$content['eacl']['rights'] = 5;
1287
			}
1288
		}
1289
		else
1290
		{
1291
			$sel_options['owner']=$sel_options['group']=$sel_options['other'] = array(
1292
				6 => lang('Read & write access'),
1293
				4 => lang('Read access only'),
1294
				0 => lang('No access'),
1295
			);
1296
		}
1297
1298
		// mergeapp
1299
		$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...
1300
		$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...
1301
		if(!empty($content['mergeapp']))
1302
		{
1303
			$content['mergeapp_effective'] = $content['mergeapp'];
1304
		}
1305
		elseif(!empty($content['mergeapp_parent']))
1306
		{
1307
			$content['mergeapp_effective'] = $content['mergeapp_parent'];
1308
		}
1309
		else
1310
		{
1311
			$content['mergeapp_effective'] = null;
1312
		}
1313
		// mergeapp select options
1314
		$mergeapp_list = Link::app_list('merge');
1315
		unset($mergeapp_list[$GLOBALS['egw_info']['flags']['currentapp']]); // exclude filemanager from list
1316
		$mergeapp_empty = !empty($content['mergeapp_parent'])
1317
			? $mergeapp_list[$content['mergeapp_parent']] . ' (parent setting)' : '';
1318
		$sel_options['mergeapp'] = array(''	=> $mergeapp_empty);
1319
		$sel_options['mergeapp'] = $sel_options['mergeapp'] + $mergeapp_list;
1320
		// mergeapp other gui options
1321
		$content['mergeapp_itempicker_disabled'] = $content['is_dir'] || empty($content['mergeapp_effective']);
1322
1323
		$preserve = $content;
1324
		if (!isset($preserve['old']))
1325
		{
1326
			$preserve['old'] = array(
1327
				'perms' => $content['perms'],
1328
				'name'  => $content['name'],
1329
				'uid'   => $content['uid'],
1330
				'gid'   => $content['gid'],
1331
				'comment' => (string)$content['comment'],
1332
				'mergeapp' => $content['mergeapp']
1333
			);
1334
			if ($cfs) foreach($cfs as $name => $data)
1335
			{
1336
				$preserve['old']['#'.$name] = (string)$content['#'.$name];
1337
			}
1338
		}
1339
		if (Vfs::$is_root)
1340
		{
1341
			$tpl->setElementAttribute('sudouser', 'label', 'Logout');
1342
			$tpl->setElementAttribute('sudouser', 'help','Log out as superuser');
1343
			// Need a more complex submit because button type is buttononly, which doesn't submit
1344
			$tpl->setElementAttribute('sudouser', 'onclick','app.filemanager.set_sudoButton(widget,"login")');
1345
1346
		}
1347
		elseif ($button == 'sudo')
1348
		{
1349
			$tpl->setElementAttribute('sudouser', 'label', 'Superuser');
1350
			$tpl->setElementAttribute('sudouser', 'help','Enter setup user and password to get root rights');
1351
			$tpl->setElementAttribute('sudouser', 'onclick','app.filemanager.set_sudoButton(widget,"logout")');
1352
		}
1353
		else if (self::is_anonymous($GLOBALS['egw_info']['user']['account_id']))
1354
		{
1355
			// Just hide sudo for anonymous users
1356
			$readonlys['sudouser'] = true;
1357
		}
1358
		if (($extra_tabs = Vfs::getExtraInfo($path,$content)))
1359
		{
1360
			// add to existing tabs in template
1361
			$tpl->setElementAttribute('tabs', 'add_tabs', true);
1362
1363
			$tabs =& $tpl->getElementAttribute('tabs','tabs');
1364
			if (true) $tabs = array();
1365
1366
			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...
1367
			{
1368
				$tabs[] = array(
1369
					'label' =>	$extra_tab['label'],
1370
					'template' =>	$extra_tab['name']
1371
				);
1372 View Code Duplication
				if ($extra_tab['data'] && is_array($extra_tab['data']))
1373
				{
1374
					$content = array_merge($content, $extra_tab['data']);
1375
				}
1376 View Code Duplication
				if ($extra_tab['preserve'] && is_array($extra_tab['preserve']))
1377
				{
1378
					$preserve = array_merge($preserve, $extra_tab['preserve']);
1379
				}
1380 View Code Duplication
				if ($extra_tab['readonlys'] && is_array($extra_tab['readonlys']))
1381
				{
1382
					$readonlys = array_merge($readonlys, $extra_tab['readonlys']);
1383
				}
1384
			}
1385
		}
1386
		Framework::window_focus();
1387
		$GLOBALS['egw_info']['flags']['app_header'] = lang('Preferences').' '.Vfs::decodePath($path);
1388
1389
		$tpl->exec('filemanager.filemanager_ui.file',$content,$sel_options,$readonlys,$preserve,2);
1390
	}
1391
1392
	/**
1393
	 * Check if the user is anonymous user
1394
	 * @param integer $account_id
1395
	 */
1396
	protected static function is_anonymous($account_id)
1397
	{
1398
		$acl = new Api\Acl($account_id);
1399
		$acl->read_repository();
1400
		return $acl->check('anonymous', 1, 'phpgwapi');
1401
	}
1402
1403
	/**
1404
	 * Remove some more dangerous actions
1405
	 * @param Array $actions
1406
	 */
1407
	protected static function restrict_anonymous_actions(&$actions)
1408
	{
1409
		$remove = array(
1410
			'delete'
1411
		);
1412
		foreach($remove as $key)
1413
		{
1414
			unset($actions[$key]);
1415
		}
1416
	}
1417
1418
	/**
1419
	 * Run given action on given path(es) and return array/object with values for keys 'msg', 'errs', 'dirs', 'files'
1420
	 *
1421
	 * @param string $action eg. 'delete', ...
1422
	 * @param array $selected selected path(s)
1423
	 * @param string $dir=null current directory
1424
	 * @see static::action()
1425
	 */
1426
	public static function ajax_action($action, $selected, $dir=null, $props=null)
1427
	{
1428
		// do we have root rights, need to run here too, as method is static and therefore does NOT run __construct
1429
		if (Api\Cache::getSession('filemanager', 'is_root'))
1430
		{
1431
			Vfs::$is_root = true;
1432
		}
1433
		$response = Api\Json\Response::get();
1434
1435
		$arr = array(
1436
			'msg' => '',
1437
			'action' => $action,
1438
			'errs' => 0,
1439
			'dirs' => 0,
1440
			'files' => 0,
1441
		);
1442
1443
		if (!isset($dir)) $dir = array_pop($selected);
1444
1445
		switch($action)
1446
		{
1447
			case 'upload':
1448
				$script_error = 0;
1449
				foreach($selected as $tmp_name => &$data)
1450
				{
1451
					$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...
1452
1453
					if(Vfs::deny_script($path))
1454
					{
1455 View Code Duplication
						if (!isset($script_error))
1456
						{
1457
							$arr['msg'] .= ($arr['msg'] ? "\n" : '').lang('You are NOT allowed to upload a script!');
1458
						}
1459
						++$script_error;
1460
						++$arr['errs'];
1461
						unset($selected[$tmp_name]);
1462
					}
1463
					elseif (Vfs::is_dir($path))
1464
					{
1465
						$data['confirm'] = 'is_dir';
1466
					}
1467
					elseif (!$data['confirmed'] && Vfs::stat($path))
1468
					{
1469
						$data['confirm'] = true;
1470
					}
1471
					else
1472
					{
1473
						if (is_dir($GLOBALS['egw_info']['server']['temp_dir']) && is_writable($GLOBALS['egw_info']['server']['temp_dir']))
1474
						{
1475
							$tmp_path = $GLOBALS['egw_info']['server']['temp_dir'] . '/' . basename($tmp_name);
1476
						}
1477
						else
1478
						{
1479
							$tmp_path = ini_get('upload_tmp_dir').'/'.basename($tmp_name);
1480
						}
1481
1482
						if (Vfs::copy_uploaded($tmp_path, $path, $props, false))
1483
						{
1484
							++$arr['files'];
1485
							$uploaded[] = $data['name'];
1486
						}
1487
						else
1488
						{
1489
							++$arr['errs'];
1490
						}
1491
					}
1492
				}
1493 View Code Duplication
				if ($arr['errs'] > $script_error)
1494
				{
1495
					$arr['msg'] .= ($arr['msg'] ? "\n" : '').lang('Error uploading file!');
1496
				}
1497
				if ($arr['files'])
1498
				{
1499
					$arr['msg'] .= ($arr['msg'] ? "\n" : '').lang('%1 successful uploaded.', implode(', ', $uploaded));
1500
				}
1501
				$arr['uploaded'] = $selected;
1502
				$arr['path'] = $dir;
1503
				$arr['props'] = $props;
1504
				break;
1505
1506
			case 'sharelink':
1507
				$share = Vfs\Sharing::create($selected, Vfs\Sharing::READONLY, basename($selected), array() );
1508
				$arr["share_link"] = $link = Vfs\Sharing::share2link($share);
1509
				$arr["template"] = Api\Etemplate\Widget\Template::rel2url('/filemanager/templates/default/share_dialog.xet');
1510
				break;
1511
1512
			// Upload, then link
1513
			case 'link':
1514
				// First upload
1515
				$arr = static::ajax_action('upload', $selected, $dir, $props);
1516
				$app_dir = Link::vfs_path($props['entry']['app'],$props['entry']['id'],'',true);
1517
1518
				foreach($arr['uploaded'] as $file)
1519
				{
1520
					$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...
1521
					if (Vfs::file_exists($target) && $app_dir)
1522
					{
1523
						if (!Vfs::file_exists($app_dir)) Vfs::mkdir($app_dir);
1524
						error_log("Symlinking $target to $app_dir");
1525
						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...
1526
					}
1527
				}
1528
				// Must return to avoid adding to $response again
1529
				return;
1530
1531
			default:
1532
				$arr['msg'] = static::action($action, $selected, $dir, $arr['errs'], $arr['dirs'], $arr['files']);
1533
		}
1534
		$response->data($arr);
1535
		//error_log(__METHOD__."('$action',".array2string($selected).') returning '.array2string($arr));
1536
		return $arr;
1537
	}
1538
1539
	/**
1540
	 * Convert perms array back to integer mode
1541
	 *
1542
	 * @param array $perms with keys owner, group, other, executable, sticky
1543
	 * @return int
1544
	 */
1545
	private function perms2mode(array $perms)
1546
	{
1547
		$mode = $perms['owner'] << 6 | $perms['group'] << 3 | $perms['other'];
1548
		if ($mode['executable'])
1549
		{
1550
			$mode |= 0111;
1551
		}
1552
		if ($mode['sticky'])
1553
		{
1554
			$mode |= 0x201;
1555
		}
1556
		return $mode;
1557
	}
1558
}
1559