1<?php
2/*
3 * Copyright Intermesh BV
4 *
5 * This file is part of Group-Office. You should have received a copy of the
6 * Group-Office license along with Group-Office. See the file /LICENSE.TXT
7 *
8 * If you have questions write an e-mail to info@intermesh.nl
9 *
10 */
11
12/**
13 * Base class for filesystem objects
14 *
15 * @package GO.base.fs
16 * @version $Id: RFC822.class.inc 7536 2011-05-31 08:37:36Z mschering $
17 * @author Merijn Schering <mschering@intermesh.nl>
18 * @copyright Copyright Intermesh BV.
19 */
20
21namespace GO\Base\Fs;
22
23
24abstract class Base{
25
26	protected $path;
27
28	const INVALID_CHARS = '/[\/:\*\?"<>|\\\]/';
29
30	/**
31	 * Constructor of a file or folder
32	 *
33	 * @param StringHelper $path The absolute path must be suplied
34	 * @throws Exception
35	 */
36	public function __construct($path) {
37
38//		\GO::debug("FS construct $path");
39
40		if(empty($path))
41			throw new \Exception("Path may not be empty in Base");
42
43		//normalize path slashes
44		if(\GO\Base\Util\Common::isWindows())
45			$path=str_replace('\\','/', $path);
46
47		if(!self::checkPathInput($path))
48			throw new \Exception("The supplied path '$path' was invalid");
49
50		$parent = dirname($path);
51		if($parent != '/')
52			$this->path=$parent;
53		else
54			$this->path='';
55
56		$this->path .= '/'.self::utf8Basename($path);
57	}
58
59	/**
60	 * Create a folder or file from a path string
61	 *
62	 * @param StringHelper $path
63	 * @return File|Folder
64	 */
65	public static function createFromPath($path){
66		if(is_file($path))
67			return new File($path);
68		else
69			return new Folder ($path);
70	}
71
72
73	/**
74	 * Get the parent folder object
75	 *
76	 * @return Folder Parent folder object
77	 */
78	public function parent(){
79
80		$parentPath = dirname($this->path);
81		if($parentPath==$this->path)
82			return false;
83
84		return new Folder($parentPath);
85	}
86
87	/**
88	 * Find the first existing parent folder.
89	 *
90	 * @return Folder
91	 */
92	public function firstExistingParent(){
93		$parent=$this;
94		while($parent = $parent->parent()){
95			if($parent->exists())
96				return $parent;
97		}
98		return false;
99	}
100
101	/**
102	 * Get a child file or folder.
103	 *
104	 * @param StringHelper $filename
105	 * @return File|Folder|boolean
106	 */
107	public function child($filename){
108		$childPath = $this->path.'/'.$filename;
109		if(is_file($childPath)){
110			return new File($childPath);
111		} elseif(is_dir($childPath)){
112			return new Folder($childPath);
113		}else
114		{
115			return false;
116		}
117	}
118
119	/**
120	 * Create a new file object. Filesystem file is not created automatically.
121	 *
122	 * @param StringHelper $filename
123	 * @param boolean $isFile
124	 * @return \File|\Folder
125	 */
126	public function createChild($filename, $isFile=true){
127		$childPath = $this->path.'/'.$filename;
128		if($isFile){
129			return new File($childPath);
130		} else{
131			return new Folder($childPath);
132		}
133	}
134
135	/**
136	 * Return absolute filesystem path
137	 *
138	 * @return String
139	 */
140	public function path(){
141		return $this->path;
142	}
143
144	/**
145	 * Return the modification unix timestamp
146	 *
147	 * @return int Unix timestamp
148	 */
149	public function mtime(){
150		return filemtime($this->path);
151	}
152	/**
153	 * Return the creation unix timestamp
154	 *
155	 * @return int Unix timestamp
156	 */
157	public function ctime(){
158		return filectime($this->path);
159	}
160
161	/**
162	 * Filesize in bytes
163	 *
164	 * @return int Filesize in bytes
165	 */
166	public function size(){
167		return filesize($this->path);
168	}
169
170	/**
171	 * Get the name of this file or folder
172	 *
173	 * @return String
174	 */
175	public function name(){
176
177		if(!function_exists('mb_substr'))
178		{
179			return basename($this->path);
180		}
181
182		if(empty($this->path))
183		{
184			return '';
185		}
186		$pos = mb_strrpos($this->path, '/');
187		if($pos===false)
188		{
189			return $this->path;
190		}else
191		{
192			return mb_substr($this->path, $pos+1);
193		}
194	}
195
196	/**
197	 * Check if the file or folder exists
198	 * @return boolean
199	 */
200	public function exists(){
201		return file_exists($this->path);
202	}
203
204	/**
205	 * Check if the file or folder is writable for the webserver user.
206	 *
207	 * @return boolean
208	 */
209	public function isWritable(){
210		return is_writable($this->path);
211	}
212
213	/**
214	 * Change owner
215	 * @param StringHelper $user
216	 * @return boolean
217	 */
218	public function chown($user){
219		return chown($this->path, $user);
220	}
221
222	/**
223	 * Change group
224	 *
225	 * @param StringHelper $group
226	 * @return boolean
227	 */
228	public function chgrp($group){
229		return chgrp($this->path, $group);
230	}
231
232	/**
233	 *
234	 * @param int $permissionsMode <p>
235	 * Note that mode is not automatically
236	 * assumed to be an octal value, so strings (such as "g+w") will
237	 * not work properly. To ensure the expected operation,
238	 * you need to prefix mode with a zero (0):
239	 * </p>
240	 *
241	 * @return boolean
242	 */
243	public function chmod($permissionsMode){
244		return chmod($this->path, $permissionsMode);
245	}
246
247	/**
248	 * Delete the file
249	 *
250	 * @return boolean
251	 */
252	public function delete(){
253		return false;
254	}
255
256	public function __toString() {
257		return $this->path;
258	}
259
260	/**
261	 * Checks if a path send as a request parameter is valid.
262	 *
263	 * @param String $path
264	 * @return boolean
265	 */
266	public static function checkPathInput($path){
267		$path = '/'.str_replace('\\','/', $path);
268		return strpos($path, '/../') === false;
269	}
270
271
272	/**
273	 * Get's the filename from a path string and works with UTF8 characters
274	 *
275	 * @param String $path
276	 * @return String
277	 */
278	public static function utf8Basename($path)
279	{
280		if(!function_exists('mb_substr'))
281		{
282			return basename($path);
283		}
284		//$path = trim($path);
285		if(substr($path,-1,1)=='/')
286		{
287			$path = substr($path,0,-1);
288		}
289		if(empty($path))
290		{
291			return '';
292		}
293		$pos = mb_strrpos($path, '/');
294		if($pos===false)
295		{
296			return $path;
297		}else
298		{
299			return mb_substr($path, $pos+1);
300		}
301	}
302
303	/**
304	 * Remove unwanted characters from a string so it can safely be used as a filename.
305	 *
306	 * @param StringHelper $filename
307	 * @return StringHelper
308	 */
309	public static function stripInvalidChars($filename, $replace=''){
310		$filename = trim(preg_replace(self::INVALID_CHARS,$replace, $filename));
311
312		//IE likes to change a double white space to a single space
313		//We must do this ourselves so the filenames will match.
314		$filename =  preg_replace('/\s+/', ' ', $filename);
315
316		//strip dots from start and end (end . is not allowed on windows)
317		$filename=trim($filename, '. ');
318
319		if(empty($filename)){
320			$filename = 'unnamed';
321		}
322
323		if(\GO::config()->convert_utf8_filenames_to_ascii)
324			$filename = \GO\Base\Util\StringHelper::utf8ToASCII($filename);
325
326		if(strlen($filename)>255)
327			$filename = substr($filename, 0,255);
328
329		return $filename;
330	}
331
332	/**
333	 * Check if this object is a folder.
334	 *
335	 * @return boolean
336	 */
337	public function isFolder(){
338//		return is_dir($this->path);
339		return is_a($this, "\GO\Base\Fs\Folder"); //works with non existing files
340	}
341
342	/**
343	 * Check if this object is a file.
344	 *
345	 * @return boolean
346	 */
347	public function isFile(){
348//		return is_file($this->path);
349		return is_a($this, "\GO\Base\Fs\File"); //works with non existing files
350	}
351
352	/**
353	 * Rename a file or folder
354	 *
355	 * @param String $name
356	 * @return boolean
357	 */
358	public function rename($name){
359		$oldPath = $this->path;
360		$newPath = dirname($this->path).'/'.$name;
361
362		if(rename($oldPath,$newPath))
363		{
364			$this->path = $newPath;
365			return true;
366		}else
367		{
368			return false;
369		}
370	}
371
372	/**
373	 * Get the path without \GO::config()->file_storage_path.
374	 *
375	 * @return StringHelper
376	 */
377	public function stripFileStoragePath(){
378		return str_replace(\GO::config()->file_storage_path,'', $this->path());
379	}
380
381	/**
382	 * Get the path without \GO::config()->root_path.
383	 *
384	 * @return StringHelper
385	 */
386	public function stripRootPath(){
387		return str_replace(\GO::config()->root_path,'', $this->path());
388	}
389
390	/**
391	 * Get the path without \GO::config()->tmpdir.
392	 *
393	 * @return StringHelper
394	 */
395	public function stripTempPath(){
396		return str_replace(\GO::config()->tmpdir,'', $this->path());
397	}
398
399	/**
400	 * Check if this is a temporary file.
401	 *
402	 * @return boolean
403	 */
404	public function isTempFile(){
405		return strpos($this->path(), \GO::config()->tmpdir)===0;
406	}
407
408}
409