Joomla Platform  13.1
Documentation des API du framework Joomla Platform
 Tout Classes Espaces de nommage Fichiers Fonctions Variables Pages
folder.php
Aller à la documentation de ce fichier.
1 <?php
2 /**
3  * @package Joomla.Platform
4  * @subpackage FileSystem
5  *
6  * @copyright Copyright (C) 2005 - 2013 Open Source Matters, Inc. All rights reserved.
7  * @license GNU General Public License version 2 or later; see LICENSE
8  */
9 
10 defined('JPATH_PLATFORM') or die;
11 
12 jimport('joomla.filesystem.path');
13 
14 /**
15  * A Folder handling class
16  *
17  * @package Joomla.Platform
18  * @subpackage FileSystem
19  * @since 11.1
20  */
21 abstract class JFolder
22 {
23  /**
24  * Copy a folder.
25  *
26  * @param string $src The path to the source folder.
27  * @param string $dest The path to the destination folder.
28  * @param string $path An optional base path to prefix to the file names.
29  * @param boolean $force Force copy.
30  * @param boolean $use_streams Optionally force folder/file overwrites.
31  *
32  * @return boolean True on success.
33  *
34  * @since 11.1
35  * @throws RuntimeException
36  */
37  public static function copy($src, $dest, $path = '', $force = false, $use_streams = false)
38  {
39  @set_time_limit(ini_get('max_execution_time'));
40 
41  $FTPOptions = JClientHelper::getCredentials('ftp');
42 
43  if ($path)
44  {
45  $src = JPath::clean($path . '/' . $src);
46  $dest = JPath::clean($path . '/' . $dest);
47  }
48 
49  // Eliminate trailing directory separators, if any
50  $src = rtrim($src, DIRECTORY_SEPARATOR);
51  $dest = rtrim($dest, DIRECTORY_SEPARATOR);
52 
53  if (!self::exists($src))
54  {
55  throw new RuntimeException('Source folder not found', -1);
56  }
57  if (self::exists($dest) && !$force)
58  {
59  throw new RuntimeException('Destination folder not found', -1);
60  }
61 
62  // Make sure the destination exists
63  if (!self::create($dest))
64  {
65  throw new RuntimeException('Cannot create destination folder', -1);
66  }
67 
68  // If we're using ftp and don't have streams enabled
69  if ($FTPOptions['enabled'] == 1 && !$use_streams)
70  {
71  // Connect the FTP client
72  $ftp = JClientFtp::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
73 
74  if (!($dh = @opendir($src)))
75  {
76  throw new RuntimeException('Cannot open source folder', -1);
77  }
78  // Walk through the directory copying files and recursing into folders.
79  while (($file = readdir($dh)) !== false)
80  {
81  $sfid = $src . '/' . $file;
82  $dfid = $dest . '/' . $file;
83 
84  switch (filetype($sfid))
85  {
86  case 'dir':
87  if ($file != '.' && $file != '..')
88  {
89  $ret = self::copy($sfid, $dfid, null, $force);
90 
91  if ($ret !== true)
92  {
93  return $ret;
94  }
95  }
96  break;
97 
98  case 'file':
99  // Translate path for the FTP account
100  $dfid = JPath::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $dfid), '/');
101 
102  if (!$ftp->store($sfid, $dfid))
103  {
104  throw new RuntimeException('Copy file failed', -1);
105  }
106  break;
107  }
108  }
109  }
110  else
111  {
112  if (!($dh = @opendir($src)))
113  {
114  throw new RuntimeException('Cannot open source folder', -1);
115  }
116  // Walk through the directory copying files and recursing into folders.
117  while (($file = readdir($dh)) !== false)
118  {
119  $sfid = $src . '/' . $file;
120  $dfid = $dest . '/' . $file;
121 
122  switch (filetype($sfid))
123  {
124  case 'dir':
125  if ($file != '.' && $file != '..')
126  {
127  $ret = self::copy($sfid, $dfid, null, $force, $use_streams);
128 
129  if ($ret !== true)
130  {
131  return $ret;
132  }
133  }
134  break;
135 
136  case 'file':
137  if ($use_streams)
138  {
139  $stream = JFactory::getStream();
140 
141  if (!$stream->copy($sfid, $dfid))
142  {
143  throw new RuntimeException('Cannot copy file: ' . $stream->getError(), -1);
144  }
145  }
146  else
147  {
148  if (!@copy($sfid, $dfid))
149  {
150  throw new RuntimeException('Copy file failed', -1);
151  }
152  }
153  break;
154  }
155  }
156  }
157  return true;
158  }
159 
160  /**
161  * Create a folder -- and all necessary parent folders.
162  *
163  * @param string $path A path to create from the base path.
164  * @param integer $mode Directory permissions to set for folders created. 0755 by default.
165  *
166  * @return boolean True if successful.
167  *
168  * @since 11.1
169  */
170  public static function create($path = '', $mode = 0755)
171  {
172  $FTPOptions = JClientHelper::getCredentials('ftp');
173  static $nested = 0;
174 
175  // Check to make sure the path valid and clean
176  $path = JPath::clean($path);
177 
178  // Check if parent dir exists
179  $parent = dirname($path);
180 
181  if (!self::exists($parent))
182  {
183  // Prevent infinite loops!
184  $nested++;
185 
186  if (($nested > 20) || ($parent == $path))
187  {
188  JLog::add(__METHOD__ . ': ' . JText::_('JLIB_FILESYSTEM_ERROR_FOLDER_LOOP'), JLog::WARNING, 'jerror');
189  $nested--;
190 
191  return false;
192  }
193 
194  // Create the parent directory
195  if (self::create($parent, $mode) !== true)
196  {
197  // JFolder::create throws an error
198  $nested--;
199 
200  return false;
201  }
202 
203  // OK, parent directory has been created
204  $nested--;
205  }
206 
207  // Check if dir already exists
208  if (self::exists($path))
209  {
210  return true;
211  }
212 
213  // Check for safe mode
214  if ($FTPOptions['enabled'] == 1)
215  {
216  // Connect the FTP client
217  $ftp = JClientFtp::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
218 
219  // Translate path to FTP path
220  $path = JPath::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $path), '/');
221  $ret = $ftp->mkdir($path);
222  $ftp->chmod($path, $mode);
223  }
224  else
225  {
226  // We need to get and explode the open_basedir paths
227  $obd = ini_get('open_basedir');
228 
229  // If open_basedir is set we need to get the open_basedir that the path is in
230  if ($obd != null)
231  {
232  if (IS_WIN)
233  {
234  $obdSeparator = ";";
235  }
236  else
237  {
238  $obdSeparator = ":";
239  }
240 
241  // Create the array of open_basedir paths
242  $obdArray = explode($obdSeparator, $obd);
243  $inBaseDir = false;
244 
245  // Iterate through open_basedir paths looking for a match
246  foreach ($obdArray as $test)
247  {
248  $test = JPath::clean($test);
249 
250  if (strpos($path, $test) === 0)
251  {
252  $inBaseDir = true;
253  break;
254  }
255  }
256  if ($inBaseDir == false)
257  {
258  // Return false for JFolder::create because the path to be created is not in open_basedir
259  JLog::add(__METHOD__ . ': ' . JText::_('JLIB_FILESYSTEM_ERROR_FOLDER_PATH'), JLog::WARNING, 'jerror');
260 
261  return false;
262  }
263  }
264 
265  // First set umask
266  $origmask = @umask(0);
267 
268  // Create the path
269  if (!$ret = @mkdir($path, $mode))
270  {
271  @umask($origmask);
272  JLog::add(
273  __METHOD__ . ': ' . JText::_('JLIB_FILESYSTEM_ERROR_COULD_NOT_CREATE_DIRECTORY') . 'Path: ' . $path, JLog::WARNING, 'jerror'
274  );
275 
276  return false;
277  }
278 
279  // Reset umask
280  @umask($origmask);
281  }
282  return $ret;
283  }
284 
285  /**
286  * Delete a folder.
287  *
288  * @param string $path The path to the folder to delete.
289  *
290  * @return boolean True on success.
291  *
292  * @since 11.1
293  * @throws UnexpectedValueException
294  */
295  public static function delete($path)
296  {
297  @set_time_limit(ini_get('max_execution_time'));
298 
299  // Sanity check
300  if (!$path)
301  {
302  // Bad programmer! Bad Bad programmer!
303  JLog::add(__METHOD__ . ': ' . JText::_('JLIB_FILESYSTEM_ERROR_DELETE_BASE_DIRECTORY'), JLog::WARNING, 'jerror');
304 
305  return false;
306  }
307 
308  $FTPOptions = JClientHelper::getCredentials('ftp');
309 
310  try
311  {
312  // Check to make sure the path valid and clean
313  $path = JPath::clean($path);
314  }
315  catch (UnexpectedValueException $e)
316  {
317  throw $e;
318  }
319 
320  // Is this really a folder?
321  if (!is_dir($path))
322  {
323  JLog::add(JText::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER', $path), JLog::WARNING, 'jerror');
324 
325  return false;
326  }
327 
328  // Remove all the files in folder if they exist; disable all filtering
329  $files = self::files($path, '.', false, true, array(), array());
330 
331  if (!empty($files))
332  {
333  jimport('joomla.filesystem.file');
334 
335  if (JFile::delete($files) !== true)
336  {
337  // JFile::delete throws an error
338  return false;
339  }
340  }
341 
342  // Remove sub-folders of folder; disable all filtering
343  $folders = self::folders($path, '.', false, true, array(), array());
344 
345  foreach ($folders as $folder)
346  {
347  if (is_link($folder))
348  {
349  // Don't descend into linked directories, just delete the link.
350  jimport('joomla.filesystem.file');
351 
352  if (JFile::delete($folder) !== true)
353  {
354  // JFile::delete throws an error
355  return false;
356  }
357  }
358  elseif (self::delete($folder) !== true)
359  {
360  // JFolder::delete throws an error
361  return false;
362  }
363  }
364 
365  if ($FTPOptions['enabled'] == 1)
366  {
367  // Connect the FTP client
368  $ftp = JClientFtp::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
369  }
370 
371  // In case of restricted permissions we zap it one way or the other
372  // as long as the owner is either the webserver or the ftp.
373  if (@rmdir($path))
374  {
375  $ret = true;
376  }
377  elseif ($FTPOptions['enabled'] == 1)
378  {
379  // Translate path and delete
380  $path = JPath::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $path), '/');
381 
382  // FTP connector throws an error
383  $ret = $ftp->delete($path);
384  }
385  else
386  {
387  JLog::add(JText::sprintf('JLIB_FILESYSTEM_ERROR_FOLDER_DELETE', $path), JLog::WARNING, 'jerror');
388  $ret = false;
389  }
390 
391  return $ret;
392  }
393 
394  /**
395  * Moves a folder.
396  *
397  * @param string $src The path to the source folder.
398  * @param string $dest The path to the destination folder.
399  * @param string $path An optional base path to prefix to the file names.
400  * @param boolean $use_streams Optionally use streams.
401  *
402  * @return mixed Error message on false or boolean true on success.
403  *
404  * @since 11.1
405  */
406  public static function move($src, $dest, $path = '', $use_streams = false)
407  {
408  $FTPOptions = JClientHelper::getCredentials('ftp');
409 
410  if ($path)
411  {
412  $src = JPath::clean($path . '/' . $src);
413  $dest = JPath::clean($path . '/' . $dest);
414  }
415 
416  if (!self::exists($src))
417  {
418  return JText::_('JLIB_FILESYSTEM_ERROR_FIND_SOURCE_FOLDER');
419  }
420 
421  if (self::exists($dest))
422  {
423  return JText::_('JLIB_FILESYSTEM_ERROR_FOLDER_EXISTS');
424  }
425 
426  if ($use_streams)
427  {
428  $stream = JFactory::getStream();
429 
430  if (!$stream->move($src, $dest))
431  {
432  return JText::sprintf('JLIB_FILESYSTEM_ERROR_FOLDER_RENAME', $stream->getError());
433  }
434 
435  $ret = true;
436  }
437  else
438  {
439  if ($FTPOptions['enabled'] == 1)
440  {
441  // Connect the FTP client
442  $ftp = JClientFtp::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
443 
444  // Translate path for the FTP account
445  $src = JPath::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $src), '/');
446  $dest = JPath::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $dest), '/');
447 
448  // Use FTP rename to simulate move
449  if (!$ftp->rename($src, $dest))
450  {
451  return JText::_('Rename failed');
452  }
453 
454  $ret = true;
455  }
456  else
457  {
458  if (!@rename($src, $dest))
459  {
460  return JText::_('Rename failed');
461  }
462 
463  $ret = true;
464  }
465  }
466 
467  return $ret;
468  }
469 
470  /**
471  * Wrapper for the standard file_exists function
472  *
473  * @param string $path Folder name relative to installation dir
474  *
475  * @return boolean True if path is a folder
476  *
477  * @since 11.1
478  */
479  public static function exists($path)
480  {
481  return is_dir(JPath::clean($path));
482  }
483 
484  /**
485  * Utility function to read the files in a folder.
486  *
487  * @param string $path The path of the folder to read.
488  * @param string $filter A filter for file names.
489  * @param mixed $recurse True to recursively search into sub-folders, or an integer to specify the maximum depth.
490  * @param boolean $full True to return the full path to the file.
491  * @param array $exclude Array with names of files which should not be shown in the result.
492  * @param array $excludefilter Array of filter to exclude
493  * @param boolean $naturalSort False for asort, true for natsort
494  *
495  * @return array Files in the given folder.
496  *
497  * @since 11.1
498  */
499  public static function files($path, $filter = '.', $recurse = false, $full = false, $exclude = array('.svn', 'CVS', '.DS_Store', '__MACOSX'),
500  $excludefilter = array('^\..*', '.*~'), $naturalSort = false)
501  {
502  // Check to make sure the path valid and clean
503  $path = JPath::clean($path);
504 
505  // Is the path a folder?
506  if (!is_dir($path))
507  {
508  JLog::add(JText::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER_FILES', $path), JLog::WARNING, 'jerror');
509 
510  return false;
511  }
512 
513  // Compute the excludefilter string
514  if (count($excludefilter))
515  {
516  $excludefilter_string = '/(' . implode('|', $excludefilter) . ')/';
517  }
518  else
519  {
520  $excludefilter_string = '';
521  }
522 
523  // Get the files
524  $arr = self::_items($path, $filter, $recurse, $full, $exclude, $excludefilter_string, true);
525 
526  // Sort the files based on either natural or alpha method
527  if ($naturalSort)
528  {
529  natsort($arr);
530  }
531  else
532  {
533  asort($arr);
534  }
535 
536  return array_values($arr);
537  }
538 
539  /**
540  * Utility function to read the folders in a folder.
541  *
542  * @param string $path The path of the folder to read.
543  * @param string $filter A filter for folder names.
544  * @param mixed $recurse True to recursively search into sub-folders, or an integer to specify the maximum depth.
545  * @param boolean $full True to return the full path to the folders.
546  * @param array $exclude Array with names of folders which should not be shown in the result.
547  * @param array $excludefilter Array with regular expressions matching folders which should not be shown in the result.
548  *
549  * @return array Folders in the given folder.
550  *
551  * @since 11.1
552  */
553  public static function folders($path, $filter = '.', $recurse = false, $full = false, $exclude = array('.svn', 'CVS', '.DS_Store', '__MACOSX'),
554  $excludefilter = array('^\..*'))
555  {
556  // Check to make sure the path valid and clean
557  $path = JPath::clean($path);
558 
559  // Is the path a folder?
560  if (!is_dir($path))
561  {
562  JLog::add(JText::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER_FOLDER', $path), JLog::WARNING, 'jerror');
563 
564  return false;
565  }
566 
567  // Compute the excludefilter string
568  if (count($excludefilter))
569  {
570  $excludefilter_string = '/(' . implode('|', $excludefilter) . ')/';
571  }
572  else
573  {
574  $excludefilter_string = '';
575  }
576 
577  // Get the folders
578  $arr = self::_items($path, $filter, $recurse, $full, $exclude, $excludefilter_string, false);
579 
580  // Sort the folders
581  asort($arr);
582 
583  return array_values($arr);
584  }
585 
586  /**
587  * Function to read the files/folders in a folder.
588  *
589  * @param string $path The path of the folder to read.
590  * @param string $filter A filter for file names.
591  * @param mixed $recurse True to recursively search into sub-folders, or an integer to specify the maximum depth.
592  * @param boolean $full True to return the full path to the file.
593  * @param array $exclude Array with names of files which should not be shown in the result.
594  * @param string $excludefilter_string Regexp of files to exclude
595  * @param boolean $findfiles True to read the files, false to read the folders
596  *
597  * @return array Files.
598  *
599  * @since 11.1
600  */
601  protected static function _items($path, $filter, $recurse, $full, $exclude, $excludefilter_string, $findfiles)
602  {
603  @set_time_limit(ini_get('max_execution_time'));
604 
605  $arr = array();
606 
607  // Read the source directory
608  if (!($handle = @opendir($path)))
609  {
610  return $arr;
611  }
612 
613  while (($file = readdir($handle)) !== false)
614  {
615  if ($file != '.' && $file != '..' && !in_array($file, $exclude)
616  && (empty($excludefilter_string) || !preg_match($excludefilter_string, $file)))
617  {
618  // Compute the fullpath
619  $fullpath = $path . '/' . $file;
620 
621  // Compute the isDir flag
622  $isDir = is_dir($fullpath);
623 
624  if (($isDir xor $findfiles) && preg_match("/$filter/", $file))
625  {
626  // (fullpath is dir and folders are searched or fullpath is not dir and files are searched) and file matches the filter
627  if ($full)
628  {
629  // Full path is requested
630  $arr[] = $fullpath;
631  }
632  else
633  {
634  // Filename is requested
635  $arr[] = $file;
636  }
637  }
638 
639  if ($isDir && $recurse)
640  {
641  // Search recursively
642  if (is_int($recurse))
643  {
644  // Until depth 0 is reached
645  $arr = array_merge($arr, self::_items($fullpath, $filter, $recurse - 1, $full, $exclude, $excludefilter_string, $findfiles));
646  }
647  else
648  {
649  $arr = array_merge($arr, self::_items($fullpath, $filter, $recurse, $full, $exclude, $excludefilter_string, $findfiles));
650  }
651  }
652  }
653  }
654 
655  closedir($handle);
656 
657  return $arr;
658  }
659 
660  /**
661  * Lists folder in format suitable for tree display.
662  *
663  * @param string $path The path of the folder to read.
664  * @param string $filter A filter for folder names.
665  * @param integer $maxLevel The maximum number of levels to recursively read, defaults to three.
666  * @param integer $level The current level, optional.
667  * @param integer $parent Unique identifier of the parent folder, if any.
668  *
669  * @return array Folders in the given folder.
670  *
671  * @since 11.1
672  */
673  public static function listFolderTree($path, $filter, $maxLevel = 3, $level = 0, $parent = 0)
674  {
675  $dirs = array();
676 
677  if ($level == 0)
678  {
679  $GLOBALS['_JFolder_folder_tree_index'] = 0;
680  }
681 
682  if ($level < $maxLevel)
683  {
684  $folders = self::folders($path, $filter);
685 
686  // First path, index foldernames
687  foreach ($folders as $name)
688  {
689  $id = ++$GLOBALS['_JFolder_folder_tree_index'];
690  $fullName = JPath::clean($path . '/' . $name);
691  $dirs[] = array('id' => $id, 'parent' => $parent, 'name' => $name, 'fullname' => $fullName,
692  'relname' => str_replace(JPATH_ROOT, '', $fullName));
693  $dirs2 = self::listFolderTree($fullName, $filter, $maxLevel, $level + 1, $id);
694  $dirs = array_merge($dirs, $dirs2);
695  }
696  }
697 
698  return $dirs;
699  }
700 
701  /**
702  * Makes path name safe to use.
703  *
704  * @param string $path The full path to sanitise.
705  *
706  * @return string The sanitised string.
707  *
708  * @since 11.1
709  */
710  public static function makeSafe($path)
711  {
712  $regex = array('#[^A-Za-z0-9_\\\/\(\)\[\]\{\}\#\$\^\+\.\'~`!@&=;,-]#');
713 
714  return preg_replace($regex, '', $path);
715  }
716 }