PageRenderTime 45ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/wp-content/plugins/loco-translate/src/fs/File.php

https://gitlab.com/najomie/fit-hippie
PHP | 573 lines | 371 code | 68 blank | 134 comment | 37 complexity | 1659a1d0424ce6928c91c277f5996971 MD5 | raw file
  1. <?php
  2. /**
  3. *
  4. */
  5. class Loco_fs_File {
  6. /**
  7. * @var Loco_fs_FileWriter
  8. */
  9. private $w;
  10. /**
  11. * Full original path to file
  12. * @var string
  13. */
  14. private $path;
  15. /**
  16. * Cached pathinfo() data
  17. * @var array
  18. */
  19. private $info;
  20. /**
  21. * Base path which path has been normalized against
  22. * @var string
  23. */
  24. private $base;
  25. /**
  26. * Flag set when current path is relative
  27. * @var bool
  28. */
  29. private $rel;
  30. /**
  31. * Check if a path is absolute and return fixed slashes for readability
  32. * @return string fixed path, or "" if not absolute
  33. */
  34. public static function abs( $path ){
  35. if( $path = (string) $path ){
  36. $chr1 = $path{0};
  37. // return unmodified path if starts "/"
  38. if( '/' === $chr1 ){
  39. return $path;
  40. }
  41. // Windows drive path if "X:" or network path if "\\"
  42. if( isset($path{1}) ){
  43. $chr2 = $path{1};
  44. if( ':' === $chr2 || ( '\\' === $chr1 && '\\' === $chr2 ) ){
  45. return strtoupper($chr1).$chr2.strtr( substr($path,2), '\\', '/' );
  46. }
  47. }
  48. }
  49. // else path is relative, so return falsey string
  50. return '';
  51. }
  52. /**
  53. * @internal
  54. */
  55. public function __construct( $path ){
  56. $this->setPath( $path );
  57. }
  58. /**
  59. * Internally set path value and flag whether relative or absolute
  60. */
  61. private function setPath( $path ){
  62. $path = (string) $path;
  63. if( $fixed = self::abs($path) ){
  64. $path = $fixed;
  65. $this->rel = false;
  66. }
  67. else {
  68. $this->rel = true;
  69. }
  70. if( $path !== $this->path ){
  71. $this->path = $path;
  72. $this->info = null;
  73. }
  74. return $path;
  75. }
  76. /**
  77. * @return array
  78. */
  79. public function isAbsolute(){
  80. return ! $this->rel;
  81. }
  82. /**
  83. * @internal
  84. */
  85. public function __clone(){
  86. $this->cloneWriteContext( $this->w );
  87. }
  88. /**
  89. * Copy write context with oursel
  90. * @return
  91. */
  92. private function cloneWriteContext( Loco_fs_FileWriter $context = null ){
  93. if( $context ){
  94. $context = clone $context;
  95. $this->w = $context->setFile($this);
  96. }
  97. return $this;
  98. }
  99. /**
  100. * Get file system context for operations that *modify* the file system.
  101. * Read operations and operations that stat the file will always do so directly.
  102. * @return Loco_fs_FileWriter
  103. */
  104. public function getWriteContext(){
  105. if( ! $this->w ){
  106. $this->w = new Loco_fs_FileWriter( $this );
  107. }
  108. return $this->w;
  109. }
  110. /**
  111. * @internal
  112. */
  113. private function pathinfo(){
  114. return is_array($this->info) ? $this->info : ( $this->info = pathinfo($this->path) );
  115. }
  116. /**
  117. * @return bool
  118. */
  119. public function exists(){
  120. return file_exists( $this->path );
  121. }
  122. /**
  123. * @return bool
  124. */
  125. public function writable(){
  126. return $this->getWriteContext()->writable();
  127. }
  128. /**
  129. * @return bool
  130. */
  131. public function deletable(){
  132. $parent = $this->getParent();
  133. if( $parent && $parent->writable() ){
  134. // sticky directory requires that either the file its parent is owned by effective user
  135. if( $parent->mode() & 01000 ){
  136. $writer = $this->getWriteContext();
  137. if( $writer->isDirect() && ( $uid = Loco_compat_PosixExtension::getuid() ) ){
  138. return $uid === $this->uid() || $uid === $parent->uid();
  139. }
  140. // else delete operation won't be done directly, so can't pre-empt sticky problems
  141. // TODO is it worth comparing FTP username etc.. for ownership?
  142. }
  143. // defaulting to "deletable" based on fact that parent is writable.
  144. return true;
  145. }
  146. return false;
  147. }
  148. /**
  149. * Get owner uid
  150. * @return int
  151. */
  152. public function uid(){
  153. return fileowner($this->path);
  154. }
  155. /**
  156. * Get group gid
  157. * @return int
  158. */
  159. public function gid(){
  160. return filegroup($this->path);
  161. }
  162. /**
  163. * Check if file can't be overwitten when existant, nor created when non-existant
  164. * This does not check permissions recursively as directory trees are not built implicitly
  165. * @return bool
  166. */
  167. public function locked(){
  168. if( $this->exists() ){
  169. return ! $this->writable();
  170. }
  171. if( $dir = $this->getParent() ){
  172. return ! $dir->writable();
  173. }
  174. return true;
  175. }
  176. /**
  177. * Check if full path can be built to non-existant file.
  178. * @return bool
  179. */
  180. public function creatable(){
  181. $file = $this;
  182. while( $file = $file->getParent() ){
  183. if( $file->exists() ){
  184. return $file->writable();
  185. }
  186. }
  187. return false;
  188. }
  189. /**
  190. * @return string
  191. */
  192. public function dirname(){
  193. $info = $this->pathinfo();
  194. return $info['dirname'];
  195. }
  196. /**
  197. * @return string
  198. */
  199. public function basename(){
  200. $info = $this->pathinfo();
  201. return $info['basename'];
  202. }
  203. /**
  204. * @return string
  205. */
  206. public function filename(){
  207. $info = $this->pathinfo();
  208. return $info['filename'];
  209. }
  210. /**
  211. * @return string
  212. */
  213. public function extension(){
  214. $info = $this->pathinfo();
  215. return isset($info['extension']) ? $info['extension'] : '';
  216. }
  217. /**
  218. * @return string
  219. */
  220. public function getPath(){
  221. return $this->path;
  222. }
  223. /**
  224. * @return int
  225. */
  226. public function modified(){
  227. return filemtime( $this->path );
  228. }
  229. /**
  230. * @return int
  231. */
  232. public function size(){
  233. return filesize( $this->path );
  234. }
  235. /**
  236. * @return int
  237. */
  238. public function mode(){
  239. if( is_link($this->path) ){
  240. $stat = lstat( $this->path );
  241. $mode = $stat[2];
  242. }
  243. else {
  244. $mode = fileperms($this->path);
  245. }
  246. return $mode;
  247. }
  248. /**
  249. * Set file mode
  250. * @return Loco_fs_File
  251. */
  252. public function chmod( $mode, $recursive = false ){
  253. $this->getWriteContext()->chmod( $mode, $recursive );
  254. return $this->clearStat();
  255. }
  256. /**
  257. * Clear stat cache if any file data has changed
  258. * @return Loco_fs_File
  259. */
  260. public function clearStat(){
  261. $this->info = null;
  262. // PHP 5.3.0 Added optional clear_realpath_cache and filename parameters.
  263. if( version_compare( PHP_VERSION, '5.3.0', '>=' ) ){
  264. clearstatcache( true, $this->path );
  265. }
  266. // else no choice but to drop entire stat cache
  267. else {
  268. clearstatcache();
  269. }
  270. return $this;
  271. }
  272. /**
  273. * @return string
  274. */
  275. public function __toString(){
  276. return $this->getPath();
  277. }
  278. /**
  279. * Check if passed path is equal to ours
  280. * @param string
  281. * @return bool
  282. */
  283. public function equal( $path ){
  284. return $this->path === (string) $path;
  285. }
  286. /**
  287. * Normalize path for string comparison, resolves redundant dots and slashes.
  288. * @param string path to prefix
  289. * @return string
  290. */
  291. public function normalize( $base = '' ){
  292. if( $path = self::abs($base) ){
  293. $base = $path;
  294. }
  295. if( $base !== $this->base ){
  296. $path = $this->path;
  297. if( '' === $path ){
  298. $this->setPath($base);
  299. }
  300. else {
  301. if( ! $this->rel || ! $base ){
  302. $b = array();
  303. }
  304. else {
  305. $b = self::explode( $base, array() );
  306. }
  307. $b = self::explode( $path, $b );
  308. $this->setPath( implode('/',$b) );
  309. }
  310. $this->base = $base;
  311. }
  312. return $this->path;
  313. }
  314. /**
  315. *
  316. */
  317. private static function explode( $path, array $b ){
  318. $a = explode( '/', $path );
  319. foreach( $a as $i => $s ){
  320. if( '' === $s ){
  321. if( 0 !== $i ){
  322. continue;
  323. }
  324. }
  325. if( '.' === $s ){
  326. continue;
  327. }
  328. if( '..' === $s ){
  329. if( array_pop($b) ){
  330. continue;
  331. }
  332. }
  333. $b[] = $s;
  334. }
  335. return $b;
  336. }
  337. /**
  338. * Get path relative to given location, unless path is already relative
  339. * @return string
  340. */
  341. public function getRelativePath( $base ){
  342. $path = $this->normalize();
  343. if( $abspath = self::abs($path) ){
  344. // base may needs require normalizing
  345. $file = new Loco_fs_File($base);
  346. $base = $file->normalize();
  347. $length = strlen($base);
  348. // if we are below given base path, return ./relative
  349. if( substr($path,0,$length) === $base ){
  350. ++$length;
  351. if( isset($path{$length}) ){
  352. return substr( $path, $length );
  353. }
  354. // else paths were idenitcal
  355. return '';
  356. }
  357. // else attempt to find nearest common root
  358. $i = 0;
  359. $source = explode('/',$base);
  360. $target = explode('/',$path);
  361. while( isset($source[$i]) && isset($target[$i]) && $source[$i] === $target[$i] ){
  362. $i++;
  363. }
  364. if( $i > 1 ){
  365. $depth = count($source) - $i;
  366. $build = array_merge( array_fill( 0, $depth, '..' ), array_slice( $target, $i ) );
  367. $path = implode( '/', $build );
  368. }
  369. }
  370. // else return unmodified
  371. return $path;
  372. }
  373. /**
  374. * @return bool
  375. */
  376. public function isDirectory(){
  377. if( file_exists($this->path) ){
  378. return is_dir($this->path);
  379. }
  380. return ! $this->extension();
  381. }
  382. /**
  383. * Load contents of file into a string
  384. * @return string
  385. */
  386. public function getContents(){
  387. return file_get_contents( $this->path );
  388. }
  389. /**
  390. * Check if path is under a theme directory
  391. * @return bool
  392. */
  393. public function underThemeDirectory(){
  394. return Loco_fs_Locations::getThemes()->check( $this->path );
  395. }
  396. /**
  397. * Check if path is under a plugin directory
  398. * @return bool
  399. */
  400. public function underPluginDirectory(){
  401. return Loco_fs_Locations::getPlugins()->check( $this->path );
  402. }
  403. /**
  404. * Check if path is under a global system directory
  405. * @return bool
  406. */
  407. public function underGlobalDirectory(){
  408. return Loco_fs_Locations::getGlobal()->check( $this->path );
  409. }
  410. /**
  411. * @return Loco_fs_Directory
  412. */
  413. public function getParent(){
  414. $path = $this->dirname();
  415. if( '.' !== $path && $this->path !== $path ){
  416. $dir = new Loco_fs_Directory( $path );
  417. $dir->cloneWriteContext( $this->w );
  418. return $dir;
  419. }
  420. }
  421. /**
  422. * Copy this file for real
  423. * @throws Loco_error_Exception
  424. * @return Loco_fs_File new file
  425. */
  426. public function copy( $dest ){
  427. $copy = clone $this;
  428. $copy->path = $dest;
  429. $copy->clearStat();
  430. $this->getWriteContext()->copy( $copy );
  431. return $copy;
  432. }
  433. /**
  434. * Delete this file for real
  435. * @throws Loco_error_Exception
  436. * @return Loco_fs_File
  437. */
  438. public function unlink(){
  439. $recursive = $this->isDirectory();
  440. $this->getWriteContext()->delete( $recursive );
  441. return $this->clearStat();
  442. }
  443. /**
  444. * Copy this object with an alternative file extension
  445. * @return Loco_fs_File
  446. */
  447. public function cloneExtension( $ext ){
  448. $snip = strlen( $this->extension() );
  449. $file = clone $this;
  450. if( $snip ){
  451. $file->path = substr_replace( $this->path, $ext, - $snip );
  452. }
  453. else {
  454. $file->path .= '.'.$ext;
  455. }
  456. $file->info = null;
  457. return $file;
  458. }
  459. /**
  460. * Ensure full parent directory tree exists
  461. * @return Loco_fs_Directory
  462. */
  463. public function createParent(){
  464. if( $dir = $this->getParent() ){
  465. if( ! $dir->exists() ){
  466. $dir->mkdir();
  467. }
  468. }
  469. return $dir;
  470. }
  471. /**
  472. * @return int bytes written to file
  473. */
  474. public function putContents( $data ){
  475. $this->getWriteContext()->putContents($data);
  476. $this->clearStat();
  477. return $this->size();
  478. }
  479. }