File.php 16 KB

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