1<?php
2/**
3 * EGroupware Filemanager: mounting GUI
4 *
5 * @link http://www.egroupware.org/
6 * @package filemanager
7 * @author Ralf Becker <rb-AT-stylite.de>
8 * @copyright (c) 2010-16 by Ralf Becker <rb-AT-stylite.de>
9 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
10 * @version $Id$
11 */
12
13use EGroupware\Api;
14use EGroupware\Api\Framework;
15use EGroupware\Api\Etemplate;
16use EGroupware\Stylite\Vfs\Versioning;
17use EGroupware\Api\Vfs;
18
19/**
20 * Filemanager: mounting GUI
21 */
22class filemanager_admin extends filemanager_ui
23{
24	/**
25	 * Functions callable via menuaction
26	 *
27	 * @var array
28	 */
29	public $public_functions = array(
30		'index' => true,
31		'fsck' => true,
32	);
33
34	/**
35	 * Autheticated user is setup config user
36	 *
37	 * @var boolean
38	 */
39	static protected $is_setup = false;
40
41	/**
42	 * Do we have versioning (Versioning\StreamWrapper class) available and with which schema
43	 *
44	 * @var string
45	 */
46	protected $versioning;
47
48	/**
49	 * Do not allow to (un)mount these
50	 *
51	 * @var array
52	 */
53	protected static $protected_path = array('/apps', '/templates');
54
55	/**
56	 * Constructor
57	 */
58	function __construct()
59	{
60		// make sure user has admin rights
61		if (!isset($GLOBALS['egw_info']['user']['apps']['admin']))
62		{
63			throw new Api\Exception\NoPermission\Admin();
64		}
65		// sudo handling
66		parent::__construct();
67		self::$is_setup = Api\Cache::getSession('filemanager', 'is_setup');
68
69		if (class_exists('EGroupware\Stylite\Vfs\Versioning\StreamWrapper'))
70		{
71			$this->versioning = Versioning\StreamWrapper::SCHEME;
72		}
73	}
74
75	/**
76	 * Mount GUI
77	 *
78	 * @param array $content=null
79	 * @param string $msg=''
80	 */
81	public function index(array $content=null, $msg='', $msg_type=null)
82	{
83		if (is_array($content))
84		{
85			//_debug_array($content);
86			if ($content['sudo'])
87			{
88				$msg = $this->sudo($content['user'],$content['password'],self::$is_setup) ?
89					lang('Root access granted.') : lang('Wrong username or password!');
90				$msg_type = Vfs::$is_root ? 'success' : 'error';
91			}
92			elseif ($content['etemplates'] && $GLOBALS['egw_info']['user']['apps']['admin'])
93			{
94				$path = '/etemplates';
95				$url = 'stylite.merge://default/etemplates?merge=.&lang=0&level=1&extension=xet&url=egw';
96				$backup = Vfs::$is_root;
97				Vfs::$is_root = true;
98				Vfs::mkdir($path);
99				Vfs::chgrp($path, 'Admins');
100				Vfs::chmod($path, 075);
101				$msg = Vfs::mount($url, $path) ?
102					lang('Successful mounted %1 on %2.',$url,$path) : lang('Error mounting %1 on %2!',$url,$path);
103				Vfs::$is_root = $backup;
104			}
105			elseif (Vfs::$is_root)
106			{
107				if ($content['logout'])
108				{
109					$msg = $this->sudo('','',self::$is_setup) ? 'Logout failed!' : lang('Root access stopped.');
110					$msg_type = !Vfs::$is_root ? 'success' : 'error';
111				}
112				if ($content['mounts']['disable'] || self::$is_setup && $content['mounts']['umount'])
113				{
114					if (($unmount = $content['mounts']['umount']))
115					{
116						$path = @key($content['mounts']['umount']);
117					}
118					else
119					{
120						$path = @key($content['mounts']['disable']);
121					}
122					if (!in_array($path, self::$protected_path) && $path != '/')
123					{
124						$msg = Vfs::umount($path) ?
125							lang('%1 successful unmounted.',$path) : lang('Error unmounting %1!',$path);
126					}
127					else	// re-mount / with sqlFS, to disable versioning
128					{
129						$msg = Vfs::mount($url=Vfs\Sqlfs\StreamWrapper::SCHEME.'://default'.$path,$path) ?
130							lang('Successful mounted %1 on %2.',$url,$path) : lang('Error mounting %1 on %2!',$url,$path);
131					}
132				}
133				if (($path = $content['mounts']['path']) &&
134					($content['mounts']['enable'] || self::$is_setup && $content['mounts']['mount']))
135				{
136					$url = str_replace('$path',$path,$content['mounts']['url']);
137					if (empty($url) && $this->versioning) $url = Versioning\StreamWrapper::PREFIX.$path;
138
139					if ($content['mounts']['enable'] && !$this->versioning)
140					{
141						$msg = lang('Versioning requires <a href="http://www.egroupware.org/products">Stylite EGroupware Enterprise Line (EPL)</a>!');
142						$msg_type = 'info';
143					}
144					elseif (!Vfs::file_exists($path) || !Vfs::is_dir($path))
145					{
146						$msg = lang('Path %1 not found or not a directory!',$path);
147						$msg_type = 'error';
148					}
149					// dont allow to change mount of /apps or /templates (eg. switching on versioning)
150					elseif (in_array($path, self::$protected_path))
151					{
152						$msg = lang('Permission denied!');
153						$msg_type = 'error';
154					}
155					else
156					{
157						$msg = Vfs::mount($url,$path) ?
158							lang('Successful mounted %1 on %2.',$url,$path) : lang('Error mounting %1 on %2!',$url,$path);
159					}
160				}
161				if ($content['allow_delete_versions'] != $GLOBALS['egw_info']['server']['allow_delete_versions'])
162				{
163					Api\Config::save_value('allow_delete_versions', $content['allow_delete_versions'], 'phpgwapi');
164					$GLOBALS['egw_info']['server']['allow_delete_versions'] = $content['allow_delete_versions'];
165					$msg = lang('Configuration changed.');
166				}
167			}
168			// delete old versions and deleted files
169			if ($content['delete-versions'])
170			{
171				if (!Versioning\StreamWrapper::check_delete_version(null))
172				{
173					$msg = lang('Permission denied')."\n\n".lang('You are NOT allowed to finally delete older versions and deleted files!');
174					$msg_type = 'error';
175				}
176				else
177				{
178					// we need to be root to delete files independent of permissions and ownership
179					Vfs::$is_root = true;
180					if (!Vfs::file_exists($content['versionedpath']) || !Vfs::is_dir($content['versionedpath']))
181					{
182						$msg = lang('Directory "%1" NOT found!', $content['versionedpath']);
183						$msg_type = 'error';
184					}
185					else
186					{
187						@set_time_limit(0);
188						$starttime = microtime(true);
189						$deleted = $errors = 0;
190
191						// shortcut to efficently delete every old version and deleted file
192						if ($content['versionedpath'] == '/')
193						{
194							$deleted = Versioning\StreamWrapper::purge_all_versioning($content['mtime']);
195						}
196						else
197						{
198							Vfs::find($content['versionedpath'], array(
199								'show-deleted' => true,
200								'hidden' => true,
201								'depth' => true,
202								'path_preg' => '#/\.(attic|versions)/#',
203							)+(!(int)$content['mtime'] ? array() : array(
204								'mtime' => ($content['mtime']<0?'-':'+').(int)$content['mtime'],
205							)), function($path) use (&$deleted, &$errors)
206							{
207								if (($is_dir = Vfs::is_dir($path)) && Vfs::rmdir($path) ||
208									!$is_dir && Vfs::unlink($path))
209								{
210									++$deleted;
211								}
212								else
213								{
214									++$errors;
215								}
216							});
217						}
218						$time = number_format(microtime(true)-$starttime, 1);
219						$msg = ($errors ? lang('%1 errors deleting!', $errors)."\n\n" : '').
220							lang('%1 files or directories deleted in %2 seconds.', $deleted, $time);
221						$msg_type = $errors ? 'error' : 'info';
222					}
223					Vfs::$is_root = false;
224				}
225			}
226		}
227		else
228		{
229			// defaults for deleting of older versions
230			$content['versionedpath'] = '/';
231			$content['mtime'] = 100;
232		}
233		if (true) $content = array(
234			'versionedpath' => $content['versionedpath'],
235			'mtime' => $content['mtime'],
236		);
237		if ($this->versioning)
238		{
239			// statistical information
240			$content += Versioning\StreamWrapper::summary();
241			if ($content['total_files']) $content['percent_files'] = number_format(100.0*$content['version_files']/$content['total_files'],1).'%';
242			if ($content['total_size']) $content['percent_size'] = number_format(100.0*$content['version_size']/$content['total_size'],1).'%';
243		}
244		if (!($content['is_root']=Vfs::$is_root))
245		{
246			if (empty($msg))
247			{
248				$msg = lang('You need to become root, to enable or disable versioning on a directory!');
249				$msg_type = 'info';
250			}
251			$readonlys['logout'] = $readonlys['enable'] = $readonlys['allow_delete_versions'] = true;
252		}
253		$content['is_setup'] = self::$is_setup;
254		$content['versioning'] = $this->versioning;
255		$content['allow_delete_versions'] = $GLOBALS['egw_info']['server']['allow_delete_versions'];
256		Framework::message($msg, $msg_type);
257
258		$n = 2;
259		$content['mounts'] = array();
260		foreach(Vfs::mount() as $path => $url)
261		{
262			$content['mounts'][$n++] = array(
263				'path' => $path,
264				'url'  => $url,
265			);
266			$readonlys["disable[$path]"] = !$this->versioning || !Vfs::$is_root ||
267				Vfs::parse_url($url,PHP_URL_SCHEME) != $this->versioning;
268		}
269		$readonlys['umount[/]'] = $readonlys['umount[/apps]'] = true;	// do not allow to unmount / or /apps
270		$readonlys['url'] = !self::$is_setup;
271
272		$sel_options['allow_delete_versions'] = array(
273			'root'     => lang('Superuser (root)'),
274			'admins'   => lang('Administrators'),
275			'everyone' => lang('Everyone'),
276		);
277		// show [Mount /etemplates] button for admin, if not already mounted and available
278		$readonlys['etemplates'] = !class_exists('\EGroupware\Stylite\Vfs\Merge\StreamWrapper') ||
279			($fs_tab=Vfs::mount($url)) && isset($fs_tab['/etemplates']) ||
280			!isset($GLOBALS['egw_info']['user']['apps']['admin']);
281		//_debug_array($content);
282
283		$tpl = new Etemplate('filemanager.admin');
284		$GLOBALS['egw_info']['flags']['app_header'] = lang('VFS mounts and versioning');
285		$tpl->exec('filemanager.filemanager_admin.index',$content,$sel_options,$readonlys);
286	}
287
288	/**
289	 * Run fsck on sqlfs
290	 */
291	function fsck()
292	{
293		if ($_POST['cancel'])
294		{
295			Framework::redirect_link('/admin/index.php', null, 'admin');
296		}
297		$check_only = !isset($_POST['fix']);
298
299		if (!($msgs = Vfs\Sqlfs\Utils::fsck($check_only)))
300		{
301			$msgs = lang('Filesystem check reported no problems.');
302		}
303		$content = '<p>'.implode("</p>\n<p>", (array)$msgs)."</p>\n";
304
305		$content .= Api\Html::form('<p>'.($check_only&&is_array($msgs) ?
306			Api\Html::submit_button('fix', lang('Fix reported problems')) : '').
307			Api\Html::submit_button('cancel', lang('Cancel')).'</p>',
308			'','/index.php',array('menuaction'=>'filemanager.filemanager_admin.fsck'));
309
310		$GLOBALS['egw']->framework->render($content, lang('Admin').' - '.lang('Check virtual filesystem'), true);
311	}
312}