Development

/branches/1.4/lib/task/sfFilesystem.class.php

You must first sign up to be able to contribute.

root/branches/1.4/lib/task/sfFilesystem.class.php

Revision 31247, 11.6 kB (checked in by fabien, 4 years ago)

fixed mbstring problem in sfFilesystem (closes #9139 - based on a patch from nresni)

  • Property svn:mime-type set to text/x-php
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1 <?php
2
3 /*
4  * This file is part of the symfony package.
5  * (c) 2004-2006 Fabien Potencier <fabien.potencier@symfony-project.com>
6  *
7  * For the full copyright and license information, please view the LICENSE
8  * file that was distributed with this source code.
9  */
10
11 /**
12  * sfFilesystem provides basic utility to manipulate the file system.
13  *
14  * @package    symfony
15  * @subpackage util
16  * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
17  * @version    SVN: $Id$
18  */
19 class sfFilesystem
20 {
21   protected
22     $dispatcher = null,
23     $formatter  = null;
24
25   /**
26    * Constructor.
27    *
28    * @param sfEventDispatcher $dispatcher  An sfEventDispatcher instance
29    * @param sfFormatter       $formatter   An sfFormatter instance
30    */
31   public function __construct(sfEventDispatcher $dispatcher = null, sfFormatter $formatter = null)
32   {
33     $this->dispatcher = $dispatcher;
34     $this->formatter = $formatter;
35   }
36
37   /**
38    * Copies a file.
39    *
40    * This method only copies the file if the origin file is newer than the target file.
41    *
42    * By default, if the target already exists, it is not overriden.
43    *
44    * To override existing files, pass the "override" option.
45    *
46    * @param string $originFile  The original filename
47    * @param string $targetFile  The target filename
48    * @param array  $options     An array of options
49    */
50   public function copy($originFile, $targetFile, $options = array())
51   {
52     if (!array_key_exists('override', $options))
53     {
54       $options['override'] = false;
55     }
56
57     // we create target_dir if needed
58     if (!is_dir(dirname($targetFile)))
59     {
60       $this->mkdirs(dirname($targetFile));
61     }
62
63     $mostRecent = false;
64     if (file_exists($targetFile))
65     {
66       $statTarget = stat($targetFile);
67       $stat_origin = stat($originFile);
68       $mostRecent = ($stat_origin['mtime'] > $statTarget['mtime']) ? true : false;
69     }
70
71     if ($options['override'] || !file_exists($targetFile) || $mostRecent)
72     {
73       $this->logSection('file+', $targetFile);
74       copy($originFile, $targetFile);
75     }
76   }
77
78   /**
79    * Creates a directory recursively.
80    *
81    * @param  string $path  The directory path
82    * @param  int    $mode  The directory mode
83    *
84    * @return bool true if the directory has been created, false otherwise
85    */
86   public function mkdirs($path, $mode = 0777)
87   {
88     if (is_dir($path))
89     {
90       return true;
91     }
92
93     $this->logSection('dir+', $path);
94
95     return @mkdir($path, $mode, true);
96   }
97
98   /**
99    * Creates empty files.
100    *
101    * @param mixed $files  The filename, or an array of filenames
102    */
103   public function touch($files)
104   {
105     if (!is_array($files))
106     {
107       $files = array($files);
108     }
109
110     foreach ($files as $file)
111     {
112       $this->logSection('file+', $file);
113
114       touch($file);
115     }
116   }
117
118   /**
119    * Removes files or directories.
120    *
121    * @param mixed $files  A filename or an array of files to remove
122    */
123   public function remove($files)
124   {
125     if (!is_array($files))
126     {
127       $files = array($files);
128     }
129
130     $files = array_reverse($files);
131     foreach ($files as $file)
132     {
133       if (is_dir($file) && !is_link($file))
134       {
135         $this->logSection('dir-', $file);
136
137         rmdir($file);
138       }
139       else
140       {
141         $this->logSection(is_link($file) ? 'link-' : 'file-', $file);
142
143         unlink($file);
144       }
145     }
146   }
147
148   /**
149    * Change mode for an array of files or directories.
150    *
151    * @param array   $files  An array of files or directories
152    * @param integer $mode   The new mode
153    * @param integer $umask  The mode mask (octal)
154    */
155   public function chmod($files, $mode, $umask = 0000)
156   {
157     $currentUmask = umask();
158     umask($umask);
159
160     if (!is_array($files))
161     {
162       $files = array($files);
163     }
164
165     foreach ($files as $file)
166     {
167       $this->logSection(sprintf('chmod %o', $mode), $file);
168       chmod($file, $mode);
169     }
170
171     umask($currentUmask);
172   }
173
174   /**
175    * Renames a file.
176    *
177    * @param string $origin  The origin filename
178    * @param string $target  The new filename
179    */
180   public function rename($origin, $target)
181   {
182     // we check that target does not exist
183     if (is_readable($target))
184     {
185       throw new sfException(sprintf('Cannot rename because the target "%s" already exist.', $target));
186     }
187
188     $this->logSection('rename', $origin.' > '.$target);
189     rename($origin, $target);
190   }
191
192   /**
193    * Creates a symbolic link or copy a directory.
194    *
195    * @param string $originDir      The origin directory path
196    * @param string $targetDir      The symbolic link name
197    * @param bool   $copyOnWindows  Whether to copy files if on windows
198    */
199   public function symlink($originDir, $targetDir, $copyOnWindows = false)
200   {
201     if ('\\' == DIRECTORY_SEPARATOR && $copyOnWindows)
202     {
203       $finder = sfFinder::type('any');
204       $this->mirror($originDir, $targetDir, $finder);
205       return;
206     }
207
208     $ok = false;
209     if (is_link($targetDir))
210     {
211       if (readlink($targetDir) != $originDir)
212       {
213         unlink($targetDir);
214       }
215       else
216       {
217         $ok = true;
218       }
219     }
220
221     if (!$ok)
222     {
223       $this->logSection('link+', $targetDir);
224       symlink($originDir, $targetDir);
225     }
226   }
227
228   /**
229    * Creates a symbolic link using a relative path if possible.
230    *
231    * @param string $originDir      The origin directory path
232    * @param string $targetDir      The symbolic link name
233    * @param bool   $copyOnWindows  Whether to copy files if on windows
234    */
235   public function relativeSymlink($originDir, $targetDir, $copyOnWindows = false)
236   {
237     if ('\\' != DIRECTORY_SEPARATOR || !$copyOnWindows)
238     {
239       $originDir = $this->calculateRelativeDir($targetDir, $originDir);
240     }
241
242     $this->symlink($originDir, $targetDir, $copyOnWindows);
243   }
244
245   /**
246    * Mirrors a directory to another.
247    *
248    * @param string   $originDir  The origin directory
249    * @param string   $targetDir  The target directory
250    * @param sfFinder $finder     An sfFinder instance
251    * @param array    $options    An array of options (see copy())
252    */
253   public function mirror($originDir, $targetDir, $finder, $options = array())
254   {
255     foreach ($finder->relative()->in($originDir) as $file)
256     {
257       if (is_dir($originDir.DIRECTORY_SEPARATOR.$file))
258       {
259         $this->mkdirs($targetDir.DIRECTORY_SEPARATOR.$file);
260       }
261       else if (is_file($originDir.DIRECTORY_SEPARATOR.$file))
262       {
263         $this->copy($originDir.DIRECTORY_SEPARATOR.$file, $targetDir.DIRECTORY_SEPARATOR.$file, $options);
264       }
265       else if (is_link($originDir.DIRECTORY_SEPARATOR.$file))
266       {
267         $this->symlink($originDir.DIRECTORY_SEPARATOR.$file, $targetDir.DIRECTORY_SEPARATOR.$file);
268       }
269       else
270       {
271         throw new sfException(sprintf('Unable to guess "%s" file type.', $file));
272       }
273     }
274   }
275
276   /**
277    * Executes a shell command.
278    *
279    * @param string $cmd            The command to execute on the shell
280    * @param array  $stdoutCallback A callback for stdout output
281    * @param array  $stderrCallback A callback for stderr output
282    *
283    * @return array An array composed of the content output and the error output
284    */
285   public function execute($cmd, $stdoutCallback = null, $stderrCallback = null)
286   {
287     $this->logSection('exec ', $cmd);
288
289     $descriptorspec = array(
290       1 => array('pipe', 'w'), // stdout
291       2 => array('pipe', 'w'), // stderr
292     );
293
294     $process = proc_open($cmd, $descriptorspec, $pipes);
295     if (!is_resource($process))
296     {
297       throw new RuntimeException('Unable to execute the command.');
298     }
299
300     stream_set_blocking($pipes[1], false);
301     stream_set_blocking($pipes[2], false);
302
303     $output = '';
304     $err = '';
305     while (!feof($pipes[1]) || !feof($pipes[2]))
306     {
307       foreach ($pipes as $key => $pipe)
308       {
309         if (!$line = fread($pipe, 128))
310         {
311           continue;
312         }
313
314         if (1 == $key)
315         {
316           // stdout
317           $output .= $line;
318           if ($stdoutCallback)
319           {
320             call_user_func($stdoutCallback, $line);
321           }
322         }
323         else
324         {
325           // stderr
326           $err .= $line;
327           if ($stderrCallback)
328           {
329             call_user_func($stderrCallback, $line);
330           }
331         }
332       }
333
334       usleep(100000);
335     }
336
337     fclose($pipes[1]);
338     fclose($pipes[2]);
339
340     if (($return = proc_close($process)) > 0)
341     {
342       throw new RuntimeException('Problem executing command.', $return);
343     }
344
345     return array($output, $err);
346   }
347
348   /**
349    * Replaces tokens in an array of files.
350    *
351    * @param array  $files       An array of filenames
352    * @param string $beginToken  The begin token delimiter
353    * @param string $endToken    The end token delimiter
354    * @param array  $tokens      An array of token/value pairs
355    */
356   public function replaceTokens($files, $beginToken, $endToken, $tokens)
357   {
358     if (!is_array($files))
359     {
360       $files = array($files);
361     }
362
363     foreach ($files as $file)
364     {
365       $content = file_get_contents($file);
366       foreach ($tokens as $key => $value)
367       {
368         $content = str_replace($beginToken.$key.$endToken, $value, $content, $count);
369       }
370
371       $this->logSection('tokens', $file);
372
373       file_put_contents($file, $content);
374     }
375   }
376
377   /**
378    * Logs a message in a section.
379    *
380    * @param string $section  The section name
381    * @param string $message  The message
382    * @param int    $size     The maximum size of a line
383    */
384   protected function logSection($section, $message, $size = null)
385   {
386     if (!$this->dispatcher)
387     {
388       return;
389     }
390
391     $message = $this->formatter ? $this->formatter->formatSection($section, $message, $size) : $section.' '.$message."\n";
392
393     $this->dispatcher->notify(new sfEvent($this, 'command.log', array($message)));
394   }
395
396   /**
397    * Calculates the relative path from one to another directory.
398    *
399    * If the paths share no common path the absolute target dir is returned.
400    *
401    * @param string $from The directory from which to calculate the relative path
402    * @param string $to   The target directory
403    *
404    * @return string
405    */
406   protected function calculateRelativeDir($from, $to)
407   {
408     $from = $this->canonicalizePath($from);
409     $to = $this->canonicalizePath($to);
410
411     $commonLength = 0;
412     $minPathLength = min(strlen($from), strlen($to));
413
414     // count how many chars the strings have in common
415     for ($i = 0; $i < $minPathLength; $i++)
416     {
417       if ($from[$i] != $to[$i])
418       {
419         break;
420       }
421
422       if (DIRECTORY_SEPARATOR == $from[$i])
423       {
424         $commonLength = $i + 1;
425       }
426     }
427
428     if ($commonLength)
429     {
430       if (extension_loaded('mbstring'))
431       {
432         $levelUp = mb_substr_count(mb_strcut($from, $commonLength), DIRECTORY_SEPARATOR);
433       }
434       else
435       {
436         $levelUp = substr_count($from, DIRECTORY_SEPARATOR, $commonLength);
437       }
438
439       // up that many level
440       $relativeDir = str_repeat('..'.DIRECTORY_SEPARATOR, $levelUp);
441
442       // down the remaining $to path
443       $relativeDir .= substr($to, $commonLength);
444
445       return $relativeDir;
446     }
447
448     return $to;
449   }
450
451   /**
452    * @param string A filesystem path
453    *
454    * @return string
455    */
456   protected function canonicalizePath($path)
457   {
458     if (empty($path))
459     {
460       return '';
461     }
462
463     $out = array();
464     foreach (explode(DIRECTORY_SEPARATOR, $path) as $i => $fold)
465     {
466       if ('' == $fold || '.' == $fold)
467       {
468         continue;
469       }
470
471       if ('..' == $fold && $i > 0 && '..' != end($out))
472       {
473         array_pop($out);
474       }
475       else
476       {
477         $out[] = $fold;
478       }
479     }
480
481     $result  = DIRECTORY_SEPARATOR == $path[0] ? DIRECTORY_SEPARATOR : '';
482     $result .= implode(DIRECTORY_SEPARATOR, $out);
483     $result .= DIRECTORY_SEPARATOR == $path[strlen($path) - 1] ? DIRECTORY_SEPARATOR : '';
484
485     return $result;
486   }
487 }
488
Note: See TracBrowser for help on using the browser.