File.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  11. * @link https://cakephp.org CakePHP(tm) Project
  12. * @since 0.2.9
  13. * @license https://opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Filesystem;
  16. use finfo;
  17. use SplFileInfo;
  18. /**
  19. * Convenience class for reading, writing and appending to files.
  20. */
  21. class File
  22. {
  23. /**
  24. * Folder object of the file
  25. *
  26. * @var \Cake\Filesystem\Folder
  27. * @link https://book.cakephp.org/3.0/en/core-libraries/file-folder.html
  28. */
  29. public $Folder;
  30. /**
  31. * File name
  32. *
  33. * @var string
  34. * https://book.cakephp.org/3.0/en/core-libraries/file-folder.html#Cake\Filesystem\File::$name
  35. */
  36. public $name;
  37. /**
  38. * File info
  39. *
  40. * @var array
  41. * https://book.cakephp.org/3.0/en/core-libraries/file-folder.html#Cake\Filesystem\File::$info
  42. */
  43. public $info = [];
  44. /**
  45. * Holds the file handler resource if the file is opened
  46. *
  47. * @var resource|null
  48. * https://book.cakephp.org/3.0/en/core-libraries/file-folder.html#Cake\Filesystem\File::$handle
  49. */
  50. public $handle;
  51. /**
  52. * Enable locking for file reading and writing
  53. *
  54. * @var bool|null
  55. * https://book.cakephp.org/3.0/en/core-libraries/file-folder.html#Cake\Filesystem\File::$lock
  56. */
  57. public $lock;
  58. /**
  59. * Path property
  60. *
  61. * Current file's absolute path
  62. *
  63. * @var string|null
  64. * https://book.cakephp.org/3.0/en/core-libraries/file-folder.html#Cake\Filesystem\File::$path
  65. */
  66. public $path;
  67. /**
  68. * Constructor
  69. *
  70. * @param string $path Path to file
  71. * @param bool $create Create file if it does not exist (if true)
  72. * @param int $mode Mode to apply to the folder holding the file
  73. * @link https://book.cakephp.org/3.0/en/core-libraries/file-folder.html#file-api
  74. */
  75. public function __construct($path, $create = false, $mode = 0755)
  76. {
  77. $splInfo = new SplFileInfo($path);
  78. $this->Folder = new Folder($splInfo->getPath(), $create, $mode);
  79. if (!is_dir($path)) {
  80. $this->name = ltrim($splInfo->getFilename(), '/\\');
  81. }
  82. $this->pwd();
  83. $create && !$this->exists() && $this->safe($path) && $this->create();
  84. }
  85. /**
  86. * Closes the current file if it is opened
  87. */
  88. public function __destruct()
  89. {
  90. $this->close();
  91. }
  92. /**
  93. * Creates the file.
  94. *
  95. * @return bool Success
  96. */
  97. public function create()
  98. {
  99. $dir = $this->Folder->pwd();
  100. if (is_dir($dir) && is_writable($dir) && !$this->exists() && touch($this->path)) {
  101. return true;
  102. }
  103. return false;
  104. }
  105. /**
  106. * Opens the current file with a given $mode
  107. *
  108. * @param string $mode A valid 'fopen' mode string (r|w|a ...)
  109. * @param bool $force If true then the file will be re-opened even if its already opened, otherwise it won't
  110. * @return bool True on success, false on failure
  111. */
  112. public function open($mode = 'r', $force = false)
  113. {
  114. if (!$force && is_resource($this->handle)) {
  115. return true;
  116. }
  117. if ($this->exists() === false && $this->create() === false) {
  118. return false;
  119. }
  120. $this->handle = fopen($this->path, $mode);
  121. return is_resource($this->handle);
  122. }
  123. /**
  124. * Return the contents of this file as a string.
  125. *
  126. * @param string|bool $bytes where to start
  127. * @param string $mode A `fread` compatible mode.
  128. * @param bool $force If true then the file will be re-opened even if its already opened, otherwise it won't
  129. * @return string|false string on success, false on failure
  130. */
  131. public function read($bytes = false, $mode = 'rb', $force = false)
  132. {
  133. if ($bytes === false && $this->lock === null) {
  134. return file_get_contents($this->path);
  135. }
  136. if ($this->open($mode, $force) === false) {
  137. return false;
  138. }
  139. if ($this->lock !== null && flock($this->handle, LOCK_SH) === false) {
  140. return false;
  141. }
  142. if (is_int($bytes)) {
  143. return fread($this->handle, $bytes);
  144. }
  145. $data = '';
  146. while (!feof($this->handle)) {
  147. $data .= fgets($this->handle, 4096);
  148. }
  149. if ($this->lock !== null) {
  150. flock($this->handle, LOCK_UN);
  151. }
  152. if ($bytes === false) {
  153. $this->close();
  154. }
  155. return trim($data);
  156. }
  157. /**
  158. * Sets or gets the offset for the currently opened file.
  159. *
  160. * @param int|bool $offset The $offset in bytes to seek. If set to false then the current offset is returned.
  161. * @param int $seek PHP Constant SEEK_SET | SEEK_CUR | SEEK_END determining what the $offset is relative to
  162. * @return int|bool True on success, false on failure (set mode), false on failure or integer offset on success (get mode)
  163. */
  164. public function offset($offset = false, $seek = SEEK_SET)
  165. {
  166. if ($offset === false) {
  167. if (is_resource($this->handle)) {
  168. return ftell($this->handle);
  169. }
  170. } elseif ($this->open() === true) {
  171. return fseek($this->handle, $offset, $seek) === 0;
  172. }
  173. return false;
  174. }
  175. /**
  176. * Prepares an ASCII string for writing. Converts line endings to the
  177. * correct terminator for the current platform. If Windows, "\r\n" will be used,
  178. * all other platforms will use "\n"
  179. *
  180. * @param string $data Data to prepare for writing.
  181. * @param bool $forceWindows If true forces Windows new line string.
  182. * @return string The with converted line endings.
  183. */
  184. public static function prepare($data, $forceWindows = false)
  185. {
  186. $lineBreak = "\n";
  187. if (DIRECTORY_SEPARATOR === '\\' || $forceWindows === true) {
  188. $lineBreak = "\r\n";
  189. }
  190. return strtr($data, ["\r\n" => $lineBreak, "\n" => $lineBreak, "\r" => $lineBreak]);
  191. }
  192. /**
  193. * Write given data to this file.
  194. *
  195. * @param string $data Data to write to this File.
  196. * @param string $mode Mode of writing. {@link https://secure.php.net/fwrite See fwrite()}.
  197. * @param bool $force Force the file to open
  198. * @return bool Success
  199. */
  200. public function write($data, $mode = 'w', $force = false)
  201. {
  202. $success = false;
  203. if ($this->open($mode, $force) === true) {
  204. if ($this->lock !== null && flock($this->handle, LOCK_EX) === false) {
  205. return false;
  206. }
  207. if (fwrite($this->handle, $data) !== false) {
  208. $success = true;
  209. }
  210. if ($this->lock !== null) {
  211. flock($this->handle, LOCK_UN);
  212. }
  213. }
  214. return $success;
  215. }
  216. /**
  217. * Append given data string to this file.
  218. *
  219. * @param string $data Data to write
  220. * @param bool $force Force the file to open
  221. * @return bool Success
  222. */
  223. public function append($data, $force = false)
  224. {
  225. return $this->write($data, 'a', $force);
  226. }
  227. /**
  228. * Closes the current file if it is opened.
  229. *
  230. * @return bool True if closing was successful or file was already closed, otherwise false
  231. */
  232. public function close()
  233. {
  234. if (!is_resource($this->handle)) {
  235. return true;
  236. }
  237. return fclose($this->handle);
  238. }
  239. /**
  240. * Deletes the file.
  241. *
  242. * @return bool Success
  243. */
  244. public function delete()
  245. {
  246. if (is_resource($this->handle)) {
  247. fclose($this->handle);
  248. $this->handle = null;
  249. }
  250. if ($this->exists()) {
  251. return unlink($this->path);
  252. }
  253. return false;
  254. }
  255. /**
  256. * Returns the file info as an array with the following keys:
  257. *
  258. * - dirname
  259. * - basename
  260. * - extension
  261. * - filename
  262. * - filesize
  263. * - mime
  264. *
  265. * @return array File information.
  266. */
  267. public function info()
  268. {
  269. if (!$this->info) {
  270. $this->info = pathinfo($this->path);
  271. }
  272. if (!isset($this->info['filename'])) {
  273. $this->info['filename'] = $this->name();
  274. }
  275. if (!isset($this->info['filesize'])) {
  276. $this->info['filesize'] = $this->size();
  277. }
  278. if (!isset($this->info['mime'])) {
  279. $this->info['mime'] = $this->mime();
  280. }
  281. return $this->info;
  282. }
  283. /**
  284. * Returns the file extension.
  285. *
  286. * @return string|false The file extension, false if extension cannot be extracted.
  287. */
  288. public function ext()
  289. {
  290. if (!$this->info) {
  291. $this->info();
  292. }
  293. if (isset($this->info['extension'])) {
  294. return $this->info['extension'];
  295. }
  296. return false;
  297. }
  298. /**
  299. * Returns the file name without extension.
  300. *
  301. * @return string|false The file name without extension, false if name cannot be extracted.
  302. */
  303. public function name()
  304. {
  305. if (!$this->info) {
  306. $this->info();
  307. }
  308. if (isset($this->info['extension'])) {
  309. return static::_basename($this->name, '.' . $this->info['extension']);
  310. }
  311. if ($this->name) {
  312. return $this->name;
  313. }
  314. return false;
  315. }
  316. /**
  317. * Returns the file basename. simulate the php basename() for multibyte (mb_basename).
  318. *
  319. * @param string $path Path to file
  320. * @param string|null $ext The name of the extension
  321. * @return string the file basename.
  322. */
  323. protected static function _basename($path, $ext = null)
  324. {
  325. // check for multibyte string and use basename() if not found
  326. if (mb_strlen($path) === strlen($path)) {
  327. return ($ext === null)? basename($path) : basename($path, $ext);
  328. }
  329. $splInfo = new SplFileInfo($path);
  330. $name = ltrim($splInfo->getFilename(), '/\\');
  331. if ($ext === null || $ext === '') {
  332. return $name;
  333. }
  334. $ext = preg_quote($ext);
  335. $new = preg_replace("/({$ext})$/u", "", $name);
  336. // basename of '/etc/.d' is '.d' not ''
  337. return ($new === '')? $name : $new;
  338. }
  339. /**
  340. * Makes file name safe for saving
  341. *
  342. * @param string|null $name The name of the file to make safe if different from $this->name
  343. * @param string|null $ext The name of the extension to make safe if different from $this->ext
  344. * @return string The extension of the file
  345. */
  346. public function safe($name = null, $ext = null)
  347. {
  348. if (!$name) {
  349. $name = $this->name;
  350. }
  351. if (!$ext) {
  352. $ext = $this->ext();
  353. }
  354. return preg_replace("/(?:[^\w\.-]+)/", '_', static::_basename($name, $ext));
  355. }
  356. /**
  357. * Get md5 Checksum of file with previous check of Filesize
  358. *
  359. * @param int|bool $maxsize in MB or true to force
  360. * @return string|false md5 Checksum {@link https://secure.php.net/md5_file See md5_file()}, or false in case of an error
  361. */
  362. public function md5($maxsize = 5)
  363. {
  364. if ($maxsize === true) {
  365. return md5_file($this->path);
  366. }
  367. $size = $this->size();
  368. if ($size && $size < ($maxsize * 1024) * 1024) {
  369. return md5_file($this->path);
  370. }
  371. return false;
  372. }
  373. /**
  374. * Returns the full path of the file.
  375. *
  376. * @return string Full path to the file
  377. */
  378. public function pwd()
  379. {
  380. if ($this->path === null) {
  381. $dir = $this->Folder->pwd();
  382. if (is_dir($dir)) {
  383. $this->path = $this->Folder->slashTerm($dir) . $this->name;
  384. }
  385. }
  386. return $this->path;
  387. }
  388. /**
  389. * Returns true if the file exists.
  390. *
  391. * @return bool True if it exists, false otherwise
  392. */
  393. public function exists()
  394. {
  395. $this->clearStatCache();
  396. return (file_exists($this->path) && is_file($this->path));
  397. }
  398. /**
  399. * Returns the "chmod" (permissions) of the file.
  400. *
  401. * @return string|false Permissions for the file, or false in case of an error
  402. */
  403. public function perms()
  404. {
  405. if ($this->exists()) {
  406. return substr(sprintf('%o', fileperms($this->path)), -4);
  407. }
  408. return false;
  409. }
  410. /**
  411. * Returns the file size
  412. *
  413. * @return int|false Size of the file in bytes, or false in case of an error
  414. */
  415. public function size()
  416. {
  417. if ($this->exists()) {
  418. return filesize($this->path);
  419. }
  420. return false;
  421. }
  422. /**
  423. * Returns true if the file is writable.
  424. *
  425. * @return bool True if it's writable, false otherwise
  426. */
  427. public function writable()
  428. {
  429. return is_writable($this->path);
  430. }
  431. /**
  432. * Returns true if the File is executable.
  433. *
  434. * @return bool True if it's executable, false otherwise
  435. */
  436. public function executable()
  437. {
  438. return is_executable($this->path);
  439. }
  440. /**
  441. * Returns true if the file is readable.
  442. *
  443. * @return bool True if file is readable, false otherwise
  444. */
  445. public function readable()
  446. {
  447. return is_readable($this->path);
  448. }
  449. /**
  450. * Returns the file's owner.
  451. *
  452. * @return int|false The file owner, or false in case of an error
  453. */
  454. public function owner()
  455. {
  456. if ($this->exists()) {
  457. return fileowner($this->path);
  458. }
  459. return false;
  460. }
  461. /**
  462. * Returns the file's group.
  463. *
  464. * @return int|false The file group, or false in case of an error
  465. */
  466. public function group()
  467. {
  468. if ($this->exists()) {
  469. return filegroup($this->path);
  470. }
  471. return false;
  472. }
  473. /**
  474. * Returns last access time.
  475. *
  476. * @return int|false Timestamp of last access time, or false in case of an error
  477. */
  478. public function lastAccess()
  479. {
  480. if ($this->exists()) {
  481. return fileatime($this->path);
  482. }
  483. return false;
  484. }
  485. /**
  486. * Returns last modified time.
  487. *
  488. * @return int|false Timestamp of last modification, or false in case of an error
  489. */
  490. public function lastChange()
  491. {
  492. if ($this->exists()) {
  493. return filemtime($this->path);
  494. }
  495. return false;
  496. }
  497. /**
  498. * Returns the current folder.
  499. *
  500. * @return \Cake\Filesystem\Folder Current folder
  501. */
  502. public function folder()
  503. {
  504. return $this->Folder;
  505. }
  506. /**
  507. * Copy the File to $dest
  508. *
  509. * @param string $dest Absolute path to copy the file to.
  510. * @param bool $overwrite Overwrite $dest if exists
  511. * @return bool Success
  512. */
  513. public function copy($dest, $overwrite = true)
  514. {
  515. if (!$this->exists() || is_file($dest) && !$overwrite) {
  516. return false;
  517. }
  518. return copy($this->path, $dest);
  519. }
  520. /**
  521. * Gets the mime type of the file. Uses the finfo extension if
  522. * it's available, otherwise falls back to mime_content_type().
  523. *
  524. * @return false|string The mimetype of the file, or false if reading fails.
  525. */
  526. public function mime()
  527. {
  528. if (!$this->exists()) {
  529. return false;
  530. }
  531. if (class_exists('finfo')) {
  532. $finfo = new finfo(FILEINFO_MIME);
  533. $type = $finfo->file($this->pwd());
  534. if (!$type) {
  535. return false;
  536. }
  537. list($type) = explode(';', $type);
  538. return $type;
  539. }
  540. if (function_exists('mime_content_type')) {
  541. return mime_content_type($this->pwd());
  542. }
  543. return false;
  544. }
  545. /**
  546. * Clear PHP's internal stat cache
  547. *
  548. * @param bool $all Clear all cache or not. Passing false will clear
  549. * the stat cache for the current path only.
  550. * @return void
  551. */
  552. public function clearStatCache($all = false)
  553. {
  554. if ($all === false) {
  555. clearstatcache(true, $this->path);
  556. }
  557. clearstatcache();
  558. }
  559. /**
  560. * Searches for a given text and replaces the text if found.
  561. *
  562. * @param string|array $search Text(s) to search for.
  563. * @param string|array $replace Text(s) to replace with.
  564. * @return bool Success
  565. */
  566. public function replaceText($search, $replace)
  567. {
  568. if (!$this->open('r+')) {
  569. return false;
  570. }
  571. if ($this->lock !== null && flock($this->handle, LOCK_EX) === false) {
  572. return false;
  573. }
  574. $replaced = $this->write(str_replace($search, $replace, $this->read()), 'w', true);
  575. if ($this->lock !== null) {
  576. flock($this->handle, LOCK_UN);
  577. }
  578. $this->close();
  579. return $replaced;
  580. }
  581. }