depage-fs
Loading...
Searching...
No Matches
Fs.php
Go to the documentation of this file.
1<?php
2
3namespace Depage\Fs;
4
5class Fs
6{
7 protected $currentPath;
8 protected $base;
9 protected $url;
10 protected $hidden;
11 protected $streamContextOptions = array();
12 protected $streamContext;
13 public function __construct($params = array())
14 {
15 if (isset($params['scheme'])) $this->url['scheme'] = $params['scheme'];
16 if (isset($params['user'])) $this->url['user'] = $params['user'];
17 if (isset($params['pass'])) $this->url['pass'] = $params['pass'];
18 if (isset($params['host'])) $this->url['host'] = $params['host'];
19 if (isset($params['port'])) $this->url['port'] = $params['port'];
20
21 $this->hidden = (isset($params['hidden'])) ? $params['hidden'] : false;
22 $this->path = (isset($params['path'])) ? $params['path'] : '.';
23
24 $this->streamContext = stream_context_create($this->streamContextOptions);
25 }
26 public static function factory($url, $params = array())
27 {
28 $parsed = self::parseUrl($url);
29 if (is_array($parsed)) {
30 $params = array_merge($parsed, $params);
31 }
32 $scheme = isset($params['scheme']) ? $params['scheme'] : null;
33 $alias = self::schemeAlias($scheme);
34
35 $schemeClass = '\Depage\Fs\Fs' . ucfirst($alias['class']);
36 $params['scheme'] = $alias['scheme'];
37
38 return new $schemeClass($params);
39 }
40 protected static function schemeAlias($alias = '')
41 {
42 $aliases = array(
43 '' => array('class' => 'file', 'scheme' => 'file'),
44 'file' => array('class' => 'file', 'scheme' => 'file'),
45 'ftp' => array('class' => 'ftp', 'scheme' => 'ftp'),
46 'ftps' => array('class' => 'ftp', 'scheme' => 'ftps'),
47 'ssh2.sftp' => array('class' => 'ssh', 'scheme' => 'ssh2.sftp'),
48 'ssh' => array('class' => 'ssh', 'scheme' => 'ssh2.sftp'),
49 'sftp' => array('class' => 'ssh', 'scheme' => 'ssh2.sftp'),
50 );
51
52 if (array_key_exists($alias, $aliases)) {
53 $translation = $aliases[$alias];
54 } else {
55 $translation = array('class' => '', 'scheme' => $alias);
56 }
57
58 return $translation;
59 }
60
61 public function pwd()
62 {
63 $this->preCommandHook();
64
66 $url['path'] = $this->base . $this->currentPath;
67 $pwd = $this->buildUrl($url);
68
69 $this->postCommandHook();
70 return $pwd;
71 }
72 public function ls($url)
73 {
74 $this->preCommandHook();
75
76 $cleanUrl = $this->cleanUrl($url);
77 $path = str_replace($this->pwd(), '', $cleanUrl);
78 $ls = $this->lsRecursive($path, '');
79
80 $this->postCommandHook();
81 return $ls;
82 }
83 public function lsDir($path = '')
84 {
85 $this->preCommandHook();
86
87 $lsDir = $this->lsFilter($path, 'is_dir');
88
89 $this->postCommandHook();
90 return $lsDir;
91 }
92 public function lsFiles($path = '')
93 {
94 $this->preCommandHook();
95
96 $lsFiles = $this->lsFilter($path, 'is_file');
97
98 $this->postCommandHook();
99 return $lsFiles;
100 }
101 public function exists($remotePath)
102 {
103 $this->preCommandHook();
104
105 $remote = $this->cleanUrl($remotePath);
106 $exists = file_exists($remote);
107
108 $this->postCommandHook();
109 return $exists;
110 }
111 public function fileInfo($remotePath)
112 {
113 $this->preCommandHook();
114
115 $remote = $this->cleanUrl($remotePath);
116 $fileInfo = new \SplFileInfo($remote);
117
118 $this->postCommandHook();
119 return $fileInfo;
120 }
121
122 public function cd($url)
123 {
124 $this->preCommandHook();
125
126 $cleanUrl = $this->cleanUrl($url);
127
128 if (is_dir($cleanUrl) && is_readable($cleanUrl . '/.')) {
129 $this->currentPath = str_replace($this->pwd(), '', $cleanUrl) . '/';
130 } else {
131 throw new Exceptions\FsException('Directory not accessible "' . $this->cleanUrl($url, false) . '".');
132 }
133
134 $this->postCommandHook();
135 }
136 public function mkdir($pathName, $mode = 0777, $recursive = true)
137 {
138 $this->preCommandHook();
139
140 $cleanUrl = $this->cleanUrl($pathName);
141 if (!is_dir($cleanUrl)) {
142 $success = mkdir($cleanUrl, $mode, $recursive, $this->streamContext);
143
144 if (!$success && !is_dir($cleanUrl)) {
145 throw new Exceptions\FsException('Error while creating directory "' . $pathName . '".');
146 }
147 }
148
149 $this->postCommandHook();
150 }
151 public function rm($url)
152 {
153 $this->preCommandHook();
154
155 $cleanUrl = $this->cleanUrl($url);
156 $pwd = $this->pwd();
157
158 if (preg_match('/^' . preg_quote($cleanUrl, '/') . '\//', $pwd . '/')) {
159 throw new Exceptions\FsException('Cannot delete current or parent directory "' . $this->cleanUrl($pwd, false) . '".');
160 }
161 $this->rmRecursive($cleanUrl);
162
163 $this->postCommandHook();
164 }
165 public function copy($sourcePath, $targetPath)
166 {
167 $this->preCommandHook();
168
169 $source = $this->cleanUrl($sourcePath);
170 $target = $this->cleanUrl($targetPath);
171
172 if (file_exists($source)) {
173 if(file_exists($target) && is_dir($target)) {
174 $target .= '/' . $this->extractFileName($source);
175 }
176 \copy($source, $target, $this->streamContext);
177 } else {
178 throw new Exceptions\FsException('Cannot copy "' . $this->cleanUrl($sourcePath, false) . '" to "' . $this->cleanUrl($targetPath, false) . '" - source doesn\'t exist.');
179 }
180
181 $this->postCommandHook();
182 }
183 public function mv($sourcePath, $targetPath)
184 {
185 $this->preCommandHook();
186
187 $source = $this->cleanUrl($sourcePath);
188 $target = $this->cleanUrl($targetPath);
189
190 if (file_exists($source)) {
191 if(file_exists($target) && is_dir($target)) {
192 $target .= '/' . $this->extractFileName($source);
193 }
194 $this->rename($source, $target);
195 } else {
196 throw new Exceptions\FsException('Cannot move "' . $this->cleanUrl($sourcePath, false) . '" to "' . $this->cleanUrl($targetPath, false) . '" - source doesn\'t exist.');
197 }
198
199 $this->postCommandHook();
200 }
201
202 public function get($remotePath, $local = null)
203 {
204 $this->preCommandHook();
205
206 if ($local === null) {
207 $local = $this->extractFileName($remotePath);
208 }
209
210 $remote = $this->cleanUrl($remotePath);
211 copy($remote, $local, $this->streamContext);
212
213 $this->postCommandHook();
214 }
215 public function put($local, $remotePath)
216 {
217 $this->preCommandHook();
218
219 $remote = $this->cleanUrl($remotePath);
220 copy($local, $remote, $this->streamContext);
221
222 $this->postCommandHook();
223 }
224 public function getString($remotePath)
225 {
226 $this->preCommandHook();
227
228 $remote = $this->cleanUrl($remotePath);
229 $string = file_get_contents($remote, false, $this->streamContext);
230
231 $this->postCommandHook();
232 return $string;
233 }
234 public function putString($remotePath, $string)
235 {
236 $this->preCommandHook();
237
238 $remote = $this->cleanUrl($remotePath);
239 $this->file_put_contents($remote, $string, 0, $this->streamContext);
240
241 $this->postCommandHook();
242 }
243
244 public function test(&$error = null)
245 {
246 $testFile = 'depage-fs-test-file.tmp';
247 $testString = 'depage-fs-test-string';
248 $success = false;
249
250 try {
251 if (!$this->exists($testFile)) {
252 $this->putString($testFile, $testString);
253 if ($this->getString($testFile) === $testString) {
254 $this->rm($testFile);
255 $success = !$this->exists($testFile);
256 }
257 }
258 } catch (Exceptions\FsException $exception) {
259 $error = $exception->getMessage();
260 }
261
262 return $success;
263 }
264
265 protected function preCommandHook()
266 {
267 $this->lateConnect();
268 $this->setErrorHandler(true);
269 }
270 protected function postCommandHook()
271 {
272 $this->setErrorHandler(false);
273 }
274 protected function lateConnect()
275 {
276 if (!isset($this->base)) {
277 $this->setBase($this->path);
278 }
279 }
280 protected function setBase($path)
281 {
282 $cleanPath = $this->cleanPath('/' . $path);
283 $this->base = (substr($cleanPath, -1) == '/') ? $cleanPath : $cleanPath . '/';
284 }
285
286 public function depageFsErrorHandler($errno, $errstr, $errfile, $errline, array $errcontext)
287 {
288 restore_error_handler();
289 throw new Exceptions\FsException($errstr);
290 }
291 protected function setErrorHandler($start)
292 {
293 if ($start) {
294 set_error_handler(array($this, 'depageFsErrorHandler'));
295 } else {
296 restore_error_handler();
297 }
298 }
299
300 public static function parseUrl($url)
301 {
302 $parsed = parse_url($url);
303
304 // hack, parse_url (PHP 5.6.29) won't handle resource name strings
305 if (isset($parsed['fragment'])) {
306 $urlParts = explode('/', $url);
307
308 if (isset($urlParts[2]) && preg_match('/^Resource id \#([0-9]+)$/', $urlParts[2], $matches)) {
309 $urlParts[2] = $matches[1];
310 $parsed = parse_url(implode('/', $urlParts));
311 $parsed['host'] = 'Resource id #' . $parsed['host'];
312 }
313 }
314
315 // hack, parse_url matches anything after the first question mark as "query"
316 $path = (isset($parsed['path'])) ? $parsed['path'] : '';
317 $query = (isset($parsed['query'])) ? $parsed['query'] : '';
318 if (!empty($query) || preg_match('/\?$/', $url)) {
319 $parsed['path'] = $path . '?' . $query;
320 unset($parsed['query']);
321 }
322
323 return $parsed;
324 }
325 protected function cleanUrl($url, $showPass = true)
326 {
327 $parsed = self::parseUrl($url);
328 $scheme = (isset($parsed['scheme'])) ? $parsed['scheme'] : null;
329 $path = (isset($parsed['path'])) ? $parsed['path'] : null;
330
331 if ($scheme) {
332 $newUrl = $parsed;
333 $newPath = $path;
334 } else {
335 $newUrl = $this->url;
336 if (substr($url, 0, 1) == '/') {
337 $newPath = $url;
338 } else {
339 $newPath = $this->base . $this->currentPath;
340 $newPath .= (substr($path, 0, 1) == '/') ? '' : '/';
341 $newPath .= $path;
342 }
343 }
344
345 $newUrl['path'] = $this->cleanPath($newPath);
346
347 if (!preg_match(';^' . preg_quote($this->cleanPath($this->base)) . '(.*)$;', $newUrl['path'])) {
348 throw new Exceptions\FsException('Cannot leave base directory "' . $this->base . '".');
349 }
350
351 return $this->buildUrl($newUrl, $showPass);
352 }
353 protected function cleanPath($path)
354 {
355 $dirs = explode('/', $path);
356 $newDirs = array();
357
358 foreach ($dirs as $dir) {
359 if ($dir == '..') {
360 array_pop($newDirs);
361 } elseif ($dir != '.' && $dir != '') {
362 $newDirs[] = $dir;
363 }
364 }
365
366 $newPath = (substr($path, 0, 1) == '/') ? '/' : '';
367 $newPath .= implode('/', $newDirs);
368
369 return $newPath;
370 }
371 protected function buildUrl($parsed, $showPass = true)
372 {
373 $path = $parsed['scheme'] . '://';
374 $path .= !empty($parsed['user']) ? $parsed['user'] : '';
375
376 if (!empty($parsed['pass'])) {
377 $path .= ($showPass) ? ':' . $parsed['pass'] : ':...';
378 }
379
380 $path .= !empty($parsed['user']) ? '@' : '';
381 $path .= !empty($parsed['host']) ? $parsed['host'] : '';
382 $path .= !empty($parsed['port']) ? ':' . $parsed['port'] : '';
383 $path .= !empty($parsed['path']) ? $parsed['path'] : '/';
384
385 return $path;
386 }
387 protected function extractFileName($path)
388 {
389 $pathInfo = pathinfo($path);
390 $fileName = $pathInfo['filename'];
391
392 if (isset($pathInfo['extension'])) {
393 $fileName .= '.' . $pathInfo['extension'];
394 }
395
396 return $fileName;
397 }
398
399 protected function lsFilter($path = '', $function)
400 {
401 $ls = $this->ls($path);
402 $pwd = $this->pwd();
403 $lsFiltered = array_filter(
404 $ls,
405 function ($element) use ($function, $pwd) {
406 return $function($pwd . $element);
407 }
408 );
409 natcasesort($lsFiltered);
410 $sorted = array_values($lsFiltered);
411
412 return $sorted;
413 }
414 protected function matchNodesInPath($path, $pattern)
415 {
416 if (preg_match('/[' . preg_quote('*?[]') . ']/', $pattern)) {
417 $matches = array_filter(
418 $this->scandir($path),
419 function ($node) use ($pattern) { return fnmatch($pattern, $node); }
420 );
421 } else {
422 $matches = array($pattern);
423 }
424 return $matches;
425 }
426 protected function lsRecursive($path, $current)
427 {
428 $nodes = array();
429 $patterns = explode('/', $path);
430 $count = count($patterns);
431 $pwd = $this->pwd();
432
433 if ($count) {
434 $pattern = array_shift($patterns);
435 $matches = $this->matchNodesInPath($pwd . $current, $pattern);
436
437 foreach ($matches as $match) {
438 $next = ($current) ? $current . '/' . $match : $match;
439
440 if ($count === 1) {
441 $nodes[] = $next;
442 } elseif (is_dir($pwd . $next)) {
443 $nodes = array_merge(
444 $nodes,
445 $this->lsRecursive(implode('/', $patterns), $next)
446 );
447 }
448 }
449 }
450
451 return $nodes;
452 }
453 protected function rmRecursive($cleanUrl)
454 {
455 if (!file_exists($cleanUrl)) {
456 throw new Exceptions\FsException('"' . $this->cleanUrl($cleanUrl, false) . '" doesn\'t exist.');
457 } elseif (is_dir($cleanUrl)) {
458 foreach ($this->scandir($cleanUrl, true) as $nested) {
459 $this->rmRecursive($cleanUrl . '/' . $nested);
460 }
461 $this->rmdir($cleanUrl);
462 } elseif (is_file($cleanUrl)) {
463 unlink($cleanUrl, $this->streamContext);
464 }
465
466 clearstatcache(true, $cleanUrl);
467 }
468
469 protected function scandir($cleanUrl = '', $hidden = null)
470 {
471 if ($hidden === null) {
473 }
474
475 $scanDir = \scandir($cleanUrl, 0, $this->streamContext);
476 $filtered = array_diff($scanDir, array('.', '..'));
477
478 if (!$hidden) {
479 $filtered = array_filter(
480 $filtered,
481 function ($node) { return ($node[0] != '.'); }
482 );
483 }
484
485 natcasesort($filtered);
486 $sorted = array_values($filtered);
487
488 return $sorted;
489 }
490
493 protected function rmdir($url)
494 {
495 return \rmdir($url, $this->streamContext);
496 }
497
500 protected function rename($source, $target)
501 {
502 return \rename($source, $target, $this->streamContext);
503 }
504
507 public function file_put_contents($filename, $data, $flags = 0, $context = null)
508 {
509 return \file_put_contents($filename, $data, $flags, $context);
510 }
511}
512
513/* vim:set ft=php sw=4 sts=4 fdm=marker : */
extractFileName($path)
Definition Fs.php:387
$streamContextOptions
Definition Fs.php:11
rmRecursive($cleanUrl)
Definition Fs.php:453
buildUrl($parsed, $showPass=true)
Definition Fs.php:371
preCommandHook()
Definition Fs.php:265
fileInfo($remotePath)
Definition Fs.php:111
static schemeAlias($alias='')
Definition Fs.php:40
cd($url)
Definition Fs.php:122
lsFilter($path='', $function)
Definition Fs.php:399
putString($remotePath, $string)
Definition Fs.php:234
cleanPath($path)
Definition Fs.php:353
ls($url)
Definition Fs.php:72
__construct($params=array())
Definition Fs.php:13
lateConnect()
Definition Fs.php:274
rename($source, $target)
Hook, allows overriding of rename function.
Definition Fs.php:500
static factory($url, $params=array())
Definition Fs.php:26
lsRecursive($path, $current)
Definition Fs.php:426
$streamContext
Definition Fs.php:12
$currentPath
Definition Fs.php:7
copy($sourcePath, $targetPath)
Definition Fs.php:165
rmdir($url)
Hook, allows overriding of rmdir function.
Definition Fs.php:493
file_put_contents($filename, $data, $flags=0, $context=null)
Hook, allows overriding of file_put_contents function.
Definition Fs.php:507
scandir($cleanUrl='', $hidden=null)
Definition Fs.php:469
lsDir($path='')
Definition Fs.php:83
mkdir($pathName, $mode=0777, $recursive=true)
Definition Fs.php:136
getString($remotePath)
Definition Fs.php:224
depageFsErrorHandler($errno, $errstr, $errfile, $errline, array $errcontext)
Definition Fs.php:286
postCommandHook()
Definition Fs.php:270
mv($sourcePath, $targetPath)
Definition Fs.php:183
lsFiles($path='')
Definition Fs.php:92
exists($remotePath)
Definition Fs.php:101
setErrorHandler($start)
Definition Fs.php:291
put($local, $remotePath)
Definition Fs.php:215
test(&$error=null)
Definition Fs.php:244
cleanUrl($url, $showPass=true)
Definition Fs.php:325
matchNodesInPath($path, $pattern)
Definition Fs.php:414
setBase($path)
Definition Fs.php:280
rm($url)
Definition Fs.php:151
static parseUrl($url)
Definition Fs.php:300