123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894 |
- <?php
- /**
- * Copyright © Magento, Inc. All rights reserved.
- * See COPYING.txt for license details.
- */
- namespace Magento\Framework\Filesystem\Io;
- /**
- * Filesystem client
- * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
- */
- class File extends AbstractIo
- {
- /**
- * Save initial working directory
- *
- * @var string
- */
- protected $_iwd;
- /**
- * Use virtual current working directory for application integrity
- *
- * @var string
- */
- protected $_cwd;
- /**
- * Used to grep ls() output
- *
- * @const
- */
- const GREP_FILES = 'files_only';
- /**
- * Used to grep ls() output
- *
- * @const
- */
- const GREP_DIRS = 'dirs_only';
- /**
- * If this variable is set to TRUE, our library will be able to automatically create
- * non-existent directories.
- *
- * @var bool
- * @access protected
- */
- protected $_allowCreateFolders = false;
- /**
- * Stream open file pointer
- *
- * @var resource
- */
- protected $_streamHandler;
- /**
- * Stream mode filename
- *
- * @var string
- */
- protected $_streamFileName;
- /**
- * Stream mode chmod
- *
- * @var string
- */
- protected $_streamChmod;
- /**
- * Lock file
- *
- * @var bool
- */
- protected $_streamLocked = false;
- /**
- * @var \Exception
- */
- private $_streamException;
- /**
- * Destruct
- *
- * @return void
- */
- public function __destruct()
- {
- if ($this->_streamHandler) {
- $this->streamClose();
- }
- }
- /**
- * Lock file
- *
- * @param bool $exclusive
- * @return bool
- */
- public function streamLock($exclusive = true)
- {
- if (!$this->_streamHandler) {
- return false;
- }
- $this->_streamLocked = true;
- $lock = $exclusive ? LOCK_EX : LOCK_SH;
- return flock($this->_streamHandler, $lock);
- }
- /**
- * Unlock file
- *
- * @return bool
- */
- public function streamUnlock()
- {
- if (!$this->_streamHandler || !$this->_streamLocked) {
- return false;
- }
- $this->_streamLocked = false;
- return flock($this->_streamHandler, LOCK_UN);
- }
- /**
- * Binary-safe file read
- *
- * @param int $length
- * @return string|false
- */
- public function streamRead($length = 1024)
- {
- if (!$this->_streamHandler) {
- return false;
- }
- if (feof($this->_streamHandler)) {
- return false;
- }
- return @fgets($this->_streamHandler, $length);
- }
- /**
- * Gets line from file pointer and parse for CSV fields
- *
- * @param string $delimiter
- * @param string $enclosure
- * @return string|false
- */
- public function streamReadCsv($delimiter = ',', $enclosure = '"')
- {
- if (!$this->_streamHandler) {
- return false;
- }
- return @fgetcsv($this->_streamHandler, 0, $delimiter, $enclosure);
- }
- /**
- * Binary-safe file write
- *
- * @param string $str
- * @return int|false
- */
- public function streamWrite($str)
- {
- if (!$this->_streamHandler) {
- return false;
- }
- return @fwrite($this->_streamHandler, $str);
- }
- /**
- * Format line as CSV and write to file pointer
- *
- * @param array $row
- * @param string $delimiter
- * @param string $enclosure
- * @return int|false The length of the written string or false
- */
- public function streamWriteCsv(array $row, $delimiter = ',', $enclosure = '"')
- {
- if (!$this->_streamHandler) {
- return false;
- }
- /**
- * Security enhancement for CSV data processing by Excel-like applications.
- * @see https://bugzilla.mozilla.org/show_bug.cgi?id=1054702
- *
- * @var $value string|\Magento\Framework\Phrase
- */
- foreach ($row as $key => $value) {
- if (!is_string($value)) {
- $value = (string)$value;
- }
- if (isset($value[0]) && in_array($value[0], ['=', '+', '-'])) {
- $row[$key] = ' ' . $value;
- }
- }
- return @fputcsv($this->_streamHandler, $row, $delimiter, $enclosure);
- }
- /**
- * Close an open file pointer
- * Set chmod on a file
- *
- * @return bool
- */
- public function streamClose()
- {
- if (!$this->_streamHandler) {
- return false;
- }
- if ($this->_streamLocked) {
- $this->streamUnlock();
- }
- @fclose($this->_streamHandler);
- $this->chmod($this->_streamFileName, $this->_streamChmod);
- return true;
- }
- /**
- * Retrieve open file statistic
- *
- * @param string $part the part of statistic
- * @param mixed $default default value for part
- * @return array|bool
- */
- public function streamStat($part = null, $default = null)
- {
- if (!$this->_streamHandler) {
- return false;
- }
- $stat = @fstat($this->_streamHandler);
- if ($part !== null) {
- return $stat[$part] ?? $default;
- }
- return $stat;
- }
- /**
- * Retrieve stream methods exception
- *
- * @return \Exception
- */
- public function getStreamException()
- {
- return $this->_streamException;
- }
- /**
- * Open a connection
- *
- * Possible arguments:
- * - path default current path
- *
- * @param array $args
- * @return true
- */
- public function open(array $args = [])
- {
- if (!empty($args['path'])) {
- if ($args['path']) {
- if ($this->_allowCreateFolders) {
- $this->_createDestinationFolder($args['path']);
- }
- }
- }
- $this->_iwd = getcwd();
- $this->cd(!empty($args['path']) ? $args['path'] : $this->_iwd);
- return true;
- }
- /**
- * Used to set {@link _allowCreateFolders} value
- *
- * @param bool $flag
- * @access public
- * @return $this
- */
- public function setAllowCreateFolders($flag)
- {
- $this->_allowCreateFolders = $flag;
- return $this;
- }
- /**
- * Close a connection
- *
- * @return true
- */
- public function close()
- {
- return true;
- }
- /**
- * Create a directory
- *
- * @param string $dir
- * @param int $mode
- * @param bool $recursive
- * @return bool
- */
- public function mkdir($dir, $mode = 0777, $recursive = true)
- {
- $this->_cwd();
- $result = @mkdir($dir, $mode, $recursive);
- $this->_iwd();
- return $result;
- }
- /**
- * Delete a directory
- *
- * @param string $dir
- * @param bool $recursive
- * @return bool
- */
- public function rmdir($dir, $recursive = false)
- {
- $this->_cwd();
- $result = self::rmdirRecursive($dir, $recursive);
- $this->_iwd();
- return $result;
- }
- /**
- * Delete a directory recursively
- * @param string $dir
- * @param bool $recursive
- * @return bool
- */
- public static function rmdirRecursive($dir, $recursive = true)
- {
- if ($recursive) {
- $result = self::_recursiveCallback($dir, ['unlink'], ['rmdir']);
- } else {
- $result = @rmdir($dir);
- }
- return $result;
- }
- /**
- * Applies specified callback for a directory/file recursively
- *
- * $fileCallback and $dirCallback format: array($callback, $parameters)
- * - $callback - callable
- * - $parameters (optional) - array with parameters to be passed to the $callback
- *
- * @param string $dir
- * @param array $fileCallback
- * @param array $dirCallback
- * @return mixed
- * @throws \InvalidArgumentException
- * @SuppressWarnings(PHPMD.CyclomaticComplexity)
- * @SuppressWarnings(PHPMD.NPathComplexity)
- */
- protected static function _recursiveCallback($dir, array $fileCallback, array $dirCallback = [])
- {
- if (empty($fileCallback) || !is_array($fileCallback) || !is_array($dirCallback)) {
- throw new \InvalidArgumentException("file/dir callback is not specified");
- }
- if (empty($dirCallback)) {
- $dirCallback = $fileCallback;
- }
- if (is_dir($dir)) {
- foreach (scandir($dir, SCANDIR_SORT_NONE) as $item) {
- if (!strcmp($item, '.') || !strcmp($item, '..')) {
- continue;
- }
- self::_recursiveCallback($dir . '/' . $item, $fileCallback, $dirCallback);
- }
- $callback = $dirCallback[0];
- if (!is_callable($callback)) {
- throw new \InvalidArgumentException("'dirCallback' parameter is not callable");
- }
- $parameters = isset($dirCallback[1]) ? $dirCallback[1] : [];
- } else {
- $callback = $fileCallback[0];
- if (!is_callable($callback)) {
- throw new \InvalidArgumentException("'fileCallback' parameter is not callable");
- }
- $parameters = isset($fileCallback[1]) ? $fileCallback[1] : [];
- }
- array_unshift($parameters, $dir);
- $result = @call_user_func_array($callback, $parameters);
- return $result;
- }
- /**
- * Get current working directory
- *
- * @return string
- */
- public function pwd()
- {
- return $this->_cwd;
- }
- /**
- * Change current working directory
- *
- * @param string $dir
- * @return true
- * @throws \Exception
- * @SuppressWarnings(PHPMD.ShortMethodName)
- */
- public function cd($dir)
- {
- if (is_dir($dir)) {
- $this->_iwd();
- $this->_cwd = realpath($dir);
- return true;
- } else {
- throw new \Exception('Unable to list current working directory.');
- }
- }
- /**
- * Read a file to result, file or stream
- *
- * If $dest is null the output will be returned.
- * Otherwise it will be saved to the file or stream and operation result is returned.
- *
- * @param string $filename
- * @param string|resource $dest
- * @return bool|string
- */
- public function read($filename, $dest = null)
- {
- $this->_cwd();
- if ($dest !== null) {
- $result = @copy($filename, $dest);
- } else {
- $result = @file_get_contents($filename);
- }
- $this->_iwd();
- return $result;
- }
- /**
- * Write a file from string, file or stream
- *
- * @param string $filename
- * @param string|resource $src
- * @param int $mode
- * @return int|bool
- * @SuppressWarnings(PHPMD.CyclomaticComplexity)
- */
- public function write($filename, $src, $mode = null)
- {
- if (is_string($src) && @is_readable($src)) {
- $src = realpath($src);
- $srcIsFile = true;
- } elseif (is_string($src) || is_resource($src)) {
- $srcIsFile = false;
- } else {
- return false;
- }
- $this->_cwd();
- if (file_exists($filename)) {
- if (!is_writeable($filename)) {
- printf('The file %s is not writable', $filename);
- return false;
- }
- } else {
- if (!is_writable(dirname($filename))) {
- printf('The directory %s is not writable', dirname($filename));
- return false;
- }
- }
- if ($srcIsFile) {
- $result = @copy($src, $filename);
- } else {
- $result = @file_put_contents($filename, $src);
- }
- if ($mode !== null && $result) {
- @chmod($filename, $mode);
- }
- $this->_iwd();
- return $result;
- }
- /**
- * Is file exists
- *
- * @param string $file
- * @param bool $onlyFile
- * @return bool
- */
- public function fileExists($file, $onlyFile = true)
- {
- $this->_cwd();
- $result = file_exists($file);
- if ($result && $onlyFile) {
- $result = is_file($file);
- }
- $this->_iwd();
- return $result;
- }
- /**
- * Tells whether the filename is writable
- *
- * @param string $path
- * @return bool
- */
- public function isWriteable($path)
- {
- $this->_cwd();
- $result = is_writeable($path);
- $this->_iwd();
- return $result;
- }
- /**
- * Get destination folder
- *
- * @param string $filePath
- * @return bool|string
- */
- public function getDestinationFolder($filePath)
- {
- preg_match('/^(.*[!\/])/', $filePath, $matches);
- if (isset($matches[0])) {
- return $matches[0];
- }
- return false;
- }
- /**
- * Create destination folder
- *
- * @param string $path
- * @return bool
- */
- public function createDestinationDir($path)
- {
- if (!$this->_allowCreateFolders) {
- return false;
- }
- return $this->_createDestinationFolder($this->getCleanPath($path));
- }
- /**
- * Check and create if not exists folder
- *
- * @param string $folder
- * @param int $mode
- * @return true
- * @throws \Exception
- */
- public function checkAndCreateFolder($folder, $mode = 0777)
- {
- if (is_dir($folder)) {
- return true;
- }
- if (!is_dir(dirname($folder))) {
- $this->checkAndCreateFolder(dirname($folder), $mode);
- }
- if (!is_dir($folder) && !$this->mkdir($folder, $mode)) {
- throw new \Exception("Unable to create directory '{$folder}'. Access forbidden.");
- }
- return true;
- }
- /**
- * Create destination folder
- *
- * @param string $destinationFolder
- * @return bool
- */
- private function _createDestinationFolder($destinationFolder)
- {
- return $this->checkAndCreateFolder($destinationFolder);
- }
- /**
- * Delete a file
- *
- * @param string $filename
- * @return bool
- * @SuppressWarnings(PHPMD.ShortMethodName)
- */
- public function rm($filename)
- {
- $this->_cwd();
- $result = @unlink($filename);
- $this->_iwd();
- return $result;
- }
- /**
- * Rename or move a directory or a file
- *
- * @param string $src
- * @param string $destination
- * @return bool
- * @SuppressWarnings(PHPMD.ShortMethodName)
- */
- public function mv($src, $destination)
- {
- $this->_cwd();
- $result = @rename($src, $destination);
- $this->_iwd();
- return $result;
- }
- /**
- * Copy a file
- *
- * @param string $src
- * @param string $destination
- * @return bool
- * @SuppressWarnings(PHPMD.ShortMethodName)
- */
- public function cp($src, $destination)
- {
- $this->_cwd();
- $result = @copy($src, $destination);
- $this->_iwd();
- return $result;
- }
- /**
- * Change mode of a directory or a file
- *
- * @param string $filename
- * @param int $mode
- * @param bool $recursive
- * @return bool
- */
- public function chmod($filename, $mode, $recursive = false)
- {
- $this->_cwd();
- if ($recursive) {
- $result = self::chmodRecursive($filename, $mode);
- } else {
- $result = @chmod($filename, $mode);
- }
- $this->_iwd();
- return $result;
- }
- /**
- * Change mode of a directory/file recursively
- *
- * @static
- * @param string $dir
- * @param int $mode
- * @return bool
- */
- public static function chmodRecursive($dir, $mode)
- {
- return self::_recursiveCallback($dir, ['chmod', [$mode]]);
- }
- /**
- * Get list of cwd subdirectories and files
- *
- * Suggestions (from moshe):
- * - Use filemtime instead of filectime for performance
- * - Change $grep to $flags and use binary flags
- * - LS_DIRS = 1
- * - LS_FILES = 2
- * - LS_ALL = 3
- *
- * @param string|null $grep
- * @return array
- * @throws \Exception
- * @SuppressWarnings(PHPMD.CyclomaticComplexity)
- * @SuppressWarnings(PHPMD.ShortMethodName)
- */
- public function ls($grep = null)
- {
- $ignoredDirectories = ['.', '..'];
- if (is_dir($this->_cwd)) {
- $dir = $this->_cwd;
- } elseif (is_dir($this->_iwd)) {
- $dir = $this->_iwd;
- } else {
- throw new \Exception('Unable to list current working directory.');
- }
- $list = [];
- $dirHandler = opendir($dir);
- if ($dirHandler) {
- while (($entry = readdir($dirHandler)) !== false) {
- $listItem = [];
- $fullPath = $dir . '/' . $entry;
- if ($grep == self::GREP_DIRS && !is_dir($fullPath)) {
- continue;
- } elseif ($grep == self::GREP_FILES && !is_file($fullPath)) {
- continue;
- } elseif (in_array($entry, $ignoredDirectories)) {
- continue;
- }
- $listItem['text'] = $entry;
- $listItem['mod_date'] = date('Y-m-d H:i:s', filectime($fullPath));
- $listItem['permissions'] = $this->_parsePermissions(fileperms($fullPath));
- $listItem['owner'] = $this->_getFileOwner($fullPath);
- if (is_file($fullPath)) {
- $pathInfo = pathinfo($fullPath);
- $listItem['size'] = filesize($fullPath);
- $listItem['leaf'] = true;
- if (isset(
- $pathInfo['extension']
- ) && in_array(
- strtolower($pathInfo['extension']),
- ['jpg', 'jpeg', 'gif', 'bmp', 'png']
- ) && $listItem['size'] > 0
- ) {
- $listItem['is_image'] = true;
- $listItem['filetype'] = $pathInfo['extension'];
- } elseif ($listItem['size'] == 0) {
- $listItem['is_image'] = false;
- $listItem['filetype'] = 'unknown';
- } elseif (isset($pathInfo['extension'])) {
- $listItem['is_image'] = false;
- $listItem['filetype'] = $pathInfo['extension'];
- } else {
- $listItem['is_image'] = false;
- $listItem['filetype'] = 'unknown';
- }
- } else {
- $listItem['leaf'] = false;
- $listItem['id'] = $fullPath;
- }
- $list[] = $listItem;
- }
- closedir($dirHandler);
- } else {
- throw new \Exception('Unable to list current working directory. Access forbidden.');
- }
- return $list;
- }
- /**
- * Change directory to current working directory
- *
- * @return void
- */
- protected function _cwd()
- {
- if ($this->_cwd) {
- chdir($this->_cwd);
- }
- }
- /**
- * Change directory to initial directory
- *
- * @return void
- */
- protected function _iwd()
- {
- if ($this->_iwd) {
- chdir($this->_iwd);
- }
- }
- /**
- * Convert integer permissions format into human readable
- *
- * @param int $mode
- * @access protected
- * @return string
- * @SuppressWarnings(PHPMD.CyclomaticComplexity)
- * @SuppressWarnings(PHPMD.NPathComplexity)
- */
- protected function _parsePermissions($mode)
- {
- if ($mode & 0x1000) {
- $type = 'p'; /* FIFO pipe */
- } elseif ($mode & 0x2000) {
- $type = 'c'; /* Character special */
- } elseif ($mode & 0x4000) {
- $type = 'd'; /* \Directory */
- } elseif ($mode & 0x6000) {
- $type = 'b'; /* Block special */
- } elseif ($mode & 0x8000) {
- $type = '-'; /* Regular */
- } elseif ($mode & 0xA000) {
- $type = 'l'; /* Symbolic Link */
- } elseif ($mode & 0xC000) {
- $type = 's'; /* Socket */
- } else {
- $type = 'u'; /* UNKNOWN */
- }
- /* Determine permissions */
- $owner['read'] = $mode & 00400 ? 'r' : '-';
- $owner['write'] = $mode & 00200 ? 'w' : '-';
- $owner['execute'] = $mode & 00100 ? 'x' : '-';
- $group['read'] = $mode & 00040 ? 'r' : '-';
- $group['write'] = $mode & 00020 ? 'w' : '-';
- $group['execute'] = $mode & 00010 ? 'x' : '-';
- $world['read'] = $mode & 00004 ? 'r' : '-';
- $world['write'] = $mode & 00002 ? 'w' : '-';
- $world['execute'] = $mode & 00001 ? 'x' : '-';
- /* Adjust for SUID, SGID and sticky bit */
- if ($mode & 0x800) {
- $owner["execute"] = $owner['execute'] == 'x' ? 's' : 'S';
- }
- if ($mode & 0x400) {
- $group["execute"] = $group['execute'] == 'x' ? 's' : 'S';
- }
- if ($mode & 0x200) {
- $world["execute"] = $world['execute'] == 'x' ? 't' : 'T';
- }
- $s = sprintf('%1s', $type);
- $s .= sprintf('%1s%1s%1s', $owner['read'], $owner['write'], $owner['execute']);
- $s .= sprintf('%1s%1s%1s', $group['read'], $group['write'], $group['execute']);
- $s .= sprintf('%1s%1s%1s', $world['read'], $world['write'], $world['execute']);
- return trim($s);
- }
- /**
- * Get file owner
- *
- * @param string $filename
- * @return string
- */
- protected function _getFileOwner($filename)
- {
- if (!function_exists('posix_getpwuid')) {
- return 'n/a';
- }
- $owner = posix_getpwuid(fileowner($filename));
- $groupInfo = posix_getgrnam(filegroup($filename));
- return $owner['name'] . ' / ' . $groupInfo;
- }
- /**
- * Get directory separator
- *
- * @return string
- */
- public function dirsep()
- {
- return '/';
- }
- /**
- * Get directory name
- *
- * @param string $file
- * @return string
- */
- public function dirname($file)
- {
- return $this->getCleanPath(dirname($file));
- }
- /**
- * Get directories list by path\
- *
- * @param string $path
- * @param int $flag
- * @return array
- */
- public function getDirectoriesList($path, $flag = GLOB_ONLYDIR)
- {
- return glob($this->getCleanPath($path) . '*', $flag);
- }
- /**
- * Get path info
- *
- * @param string $path
- * @return mixed
- */
- public function getPathInfo($path)
- {
- return pathinfo($path);
- }
- }
|