PageRenderTime 46ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/concrete/src/File/Service/File.php

http://github.com/concrete5/concrete5
PHP | 513 lines | 356 code | 37 blank | 120 comment | 53 complexity | f3193227ea9669fe9190af774c156b48 MD5 | raw file
Possible License(s): MIT, LGPL-2.1, MPL-2.0-no-copyleft-exception, BSD-3-Clause
  1. <?php
  2. namespace Concrete\Core\File\Service;
  3. use Zend\Http\Client\Adapter\Exception\TimeoutException;
  4. use Config;
  5. use Environment;
  6. use Exception;
  7. use Core;
  8. use Concrete\Core\Support\Facade\Application as CoreApplication;
  9. /**
  10. * File helper.
  11. *
  12. * Functions useful for working with files and directories.
  13. *
  14. * Used as follows:
  15. * <code>
  16. * $file = Core::make('helper/file');
  17. * $path = 'http://www.concrete5.org/tools/get_latest_version_number';
  18. * $contents = $file->getContents($path);
  19. * echo $contents;
  20. * </code>
  21. *
  22. * \@package Helpers
  23. *
  24. * @category Concrete
  25. *
  26. * @author Andrew Embler <andrew@concrete5.org>
  27. * @copyright Copyright (c) 2003-2008 Concrete5. (http://www.concrete5.org)
  28. * @license http://www.concrete5.org/license/ MIT License
  29. */
  30. class File
  31. {
  32. /**
  33. * Returns the contents of a directory.
  34. *
  35. *
  36. */
  37. public function getDirectoryContents($dir, $ignoreFiles = [], $recursive = false)
  38. {
  39. $aDir = [];
  40. if (is_dir($dir)) {
  41. $handle = opendir($dir);
  42. while (($file = readdir($handle)) !== false) {
  43. if (substr($file, 0, 1) != '.' && (!in_array($file, $ignoreFiles))) {
  44. if (is_dir($dir . '/' . $file)) {
  45. if ($recursive) {
  46. $aDir = array_merge($aDir, $this->getDirectoryContents($dir . '/' . $file, $ignoreFiles, $recursive));
  47. $file = $dir . '/' . $file;
  48. }
  49. $aDir[] = preg_replace("/\/\//si", '/', $file);
  50. } else {
  51. if ($recursive) {
  52. $file = $dir . '/' . $file;
  53. }
  54. $aDir[] = preg_replace("/\/\//si", '/', $file);
  55. }
  56. }
  57. }
  58. closedir($handle);
  59. }
  60. return $aDir;
  61. }
  62. /**
  63. * Removes the extension of a filename, uncamelcases it.
  64. *
  65. * @param string $filename
  66. *
  67. * @return string
  68. */
  69. public function unfilename($filename)
  70. {
  71. $parts = $this->splitFilename($filename);
  72. $txt = Core::make('helper/text');
  73. /* @var $txt \Concrete\Core\Utility\Service\Text */
  74. return $txt->unhandle($parts[0] . $parts[1]);
  75. }
  76. /**
  77. * Recursively copies all items in the source directory or file to the target directory.
  78. *
  79. * @param string $source Source dir/file to copy
  80. * @param string $target Place to copy the source
  81. * @param int $mode What to chmod the file to
  82. */
  83. public function copyAll($source, $target, $mode = null)
  84. {
  85. if (is_dir($source)) {
  86. if ($mode == null) {
  87. @mkdir($target, Config::get('concrete.filesystem.permissions.directory'));
  88. @chmod($target, Config::get('concrete.filesystem.permissions.directory'));
  89. } else {
  90. @mkdir($target, $mode);
  91. @chmod($target, $mode);
  92. }
  93. $d = dir($source);
  94. while (false !== ($entry = $d->read())) {
  95. if (substr($entry, 0, 1) === '.') {
  96. continue;
  97. }
  98. $Entry = $source . '/' . $entry;
  99. if (is_dir($Entry)) {
  100. $this->copyAll($Entry, $target . '/' . $entry, $mode);
  101. continue;
  102. }
  103. copy($Entry, $target . '/' . $entry);
  104. if ($mode == null) {
  105. @chmod($target . '/' . $entry, $this->getCreateFilePermissions($target)->file);
  106. } else {
  107. @chmod($target . '/' . $entry, $mode);
  108. }
  109. }
  110. $d->close();
  111. } else {
  112. if ($mode == null) {
  113. $mode = $this->getCreateFilePermissions(dirname($target))->file;
  114. }
  115. copy($source, $target);
  116. chmod($target, $mode);
  117. }
  118. }
  119. /**
  120. * Returns an object with two permissions modes (octal):
  121. * one for files: $res->file
  122. * and another for directories: $res->dir.
  123. *
  124. * @param string $path (optional)
  125. *
  126. * @return \stdClass
  127. */
  128. public function getCreateFilePermissions($path = null)
  129. {
  130. try {
  131. if (!isset($path)) {
  132. $path = DIR_FILES_UPLOADED_STANDARD;
  133. }
  134. if (!is_dir($path)) {
  135. $path = @dirname($path);
  136. }
  137. $perms = @fileperms($path);
  138. if (!$perms) {
  139. throw new Exception(t('An error occurred while attempting to determine file permissions.'));
  140. }
  141. clearstatcache();
  142. $dir_perms = substr(decoct($perms), 1);
  143. $file_perms = "0";
  144. $parts[] = substr($dir_perms, 1, 1);
  145. $parts[] = substr($dir_perms, 2, 1);
  146. $parts[] = substr($dir_perms, 3, 1);
  147. foreach ($parts as $p) {
  148. if (intval($p) % 2 == 0) {
  149. $file_perms .= $p;
  150. continue;
  151. }
  152. $file_perms .= intval($p) - 1;
  153. }
  154. } catch (Exception $e) {
  155. return false;
  156. }
  157. $res = new \stdClass();
  158. $res->file = intval($file_perms, 8);
  159. $res->dir = intval($dir_perms, 8);
  160. return $res;
  161. }
  162. /**
  163. * Removes all files from within a specified directory.
  164. *
  165. * @param string $source Directory
  166. * @param bool $inc Remove the passed directory as well or leave it alone
  167. *
  168. * @return bool Whether the methods succeeds or fails
  169. */
  170. public function removeAll($source, $inc = false)
  171. {
  172. if (!is_dir($source)) {
  173. return false;
  174. }
  175. $iterator = new \RecursiveIteratorIterator(
  176. new \RecursiveDirectoryIterator($source, \FilesystemIterator::SKIP_DOTS),
  177. \RecursiveIteratorIterator::CHILD_FIRST
  178. );
  179. foreach ($iterator as $path) {
  180. if ($path->isDir()) {
  181. if (!@rmdir($path->__toString())) {
  182. return false;
  183. }
  184. } else {
  185. if (!@unlink($path->__toString())) {
  186. return false;
  187. }
  188. }
  189. }
  190. if ($inc) {
  191. if (!@rmdir($source)) {
  192. return false;
  193. }
  194. }
  195. return true;
  196. }
  197. /**
  198. * Takes a path to a file and sends it to the browser, streaming it, and closing the HTTP connection afterwards. Basically a force download method.
  199. *
  200. * @param stings $file
  201. */
  202. public function forceDownload($file)
  203. {
  204. session_write_close();
  205. ob_clean();
  206. header('Content-type: application/octet-stream');
  207. $filename = basename($file);
  208. header("Content-Disposition: attachment; filename=\"$filename\"");
  209. header('Content-Length: ' . filesize($file));
  210. header("Pragma: public");
  211. header("Expires: 0");
  212. header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
  213. header("Cache-Control: private", false);
  214. header("Content-Transfer-Encoding: binary");
  215. header("Content-Encoding: plainbinary");
  216. // This code isn't ready yet. It will allow us to no longer force download
  217. /*
  218. $h = Core::make('helper/mime');
  219. $mimeType = $h->mimeFromExtension($this->getExtension($file));
  220. header('Content-type: ' . $mimeType);
  221. */
  222. $buffer = '';
  223. $chunk = 1024 * 1024;
  224. $handle = fopen($file, 'rb');
  225. if ($handle === false) {
  226. return false;
  227. }
  228. while (!feof($handle)) {
  229. $buffer = fread($handle, $chunk);
  230. echo $buffer;
  231. }
  232. fclose($handle);
  233. exit;
  234. }
  235. /**
  236. * Returns the full path to the temporary directory.
  237. *
  238. * @return string
  239. */
  240. public function getTemporaryDirectory()
  241. {
  242. $temp = Config::get('concrete.filesystem.temp_directory');
  243. if ($temp && @is_dir($temp)) {
  244. return $temp;
  245. }
  246. if (!is_dir(DIR_FILES_UPLOADED_STANDARD . '/tmp')) {
  247. @mkdir(DIR_FILES_UPLOADED_STANDARD . '/tmp', Config::get('concrete.filesystem.permissions.directory'));
  248. @chmod(DIR_FILES_UPLOADED_STANDARD . '/tmp', Config::get('concrete.filesystem.permissions.directory'));
  249. @touch(DIR_FILES_UPLOADED_STANDARD . '/tmp/index.html');
  250. }
  251. if (is_dir(DIR_FILES_UPLOADED_STANDARD . '/tmp') && is_writable(DIR_FILES_UPLOADED_STANDARD . '/tmp')) {
  252. return DIR_FILES_UPLOADED_STANDARD . '/tmp';
  253. }
  254. if ($temp = getenv('TMP')) {
  255. return str_replace(DIRECTORY_SEPARATOR, '/', $temp);
  256. }
  257. if ($temp = getenv('TEMP')) {
  258. return str_replace(DIRECTORY_SEPARATOR, '/', $temp);
  259. }
  260. if ($temp = getenv('TMPDIR')) {
  261. return str_replace(DIRECTORY_SEPARATOR, '/', $temp);
  262. }
  263. $temp = @tempnam(__FILE__, '');
  264. if (file_exists($temp)) {
  265. unlink($temp);
  266. return str_replace(DIRECTORY_SEPARATOR, '/', dirname($temp));
  267. }
  268. }
  269. /**
  270. * Adds content to a new line in a file. If a file is not there it will be created.
  271. *
  272. * @param string $filename
  273. * @param string $content
  274. *
  275. * @return bool
  276. */
  277. public function append($filename, $content)
  278. {
  279. return file_put_contents($filename, $content, FILE_APPEND) !== false;
  280. }
  281. /**
  282. * Just a consistency wrapper for file_get_contents
  283. * Should use curl if it exists and fopen isn't allowed (thanks Remo).
  284. *
  285. * @param string $filename
  286. * @param string $timeout
  287. *
  288. * @throws TimeoutException Request timed out
  289. *
  290. * @return string|bool Returns false in case of failure
  291. */
  292. public function getContents($file, $timeout = null)
  293. {
  294. $url = @parse_url($file);
  295. if (isset($url['scheme']) && isset($url['host'])) {
  296. $app = CoreApplication::getFacadeApplication();
  297. $client = $app->make('http/client')->setUri($file);
  298. if (!empty($timeout) && $timeout > 0) {
  299. $client->setOptions(['timeout' => $timeout]);
  300. }
  301. try {
  302. $response = $client->send();
  303. } catch (TimeoutException $x) {
  304. throw $x;
  305. } catch (Exception $x) {
  306. $response = null;
  307. }
  308. return $response ? $response->getBody() : false;
  309. } else {
  310. $contents = @file_get_contents($file);
  311. if ($contents !== false) {
  312. return $contents;
  313. }
  314. }
  315. return false;
  316. }
  317. /**
  318. * Removes contents of the file.
  319. *
  320. * @param $filename
  321. *
  322. * @return bool
  323. */
  324. public function clear($file)
  325. {
  326. return file_put_contents($file, '') !== false;
  327. }
  328. /**
  329. * Cleans up a filename and returns the cleaned up version.
  330. *
  331. * @param string $file
  332. *
  333. * @return string
  334. */
  335. public function sanitize($file)
  336. {
  337. // Let's build an ASCII-only version of name, to avoid filesystem-specific encoding issues.
  338. $asciiName = Core::make('helper/text')->asciify($file);
  339. // Let's keep only letters, numbers, underscore and dots.
  340. $asciiName = trim(preg_replace(["/[\\s]/", "/[^0-9A-Z_a-z-.]/"], ["_", ""], $asciiName));
  341. // Trim underscores at start and end
  342. $asciiName = trim($asciiName, '_');
  343. if (!strlen(str_replace('.', '', $asciiName))) {
  344. // If the resulting name is empty (or we have only dots in it)
  345. $asciiName = md5($file);
  346. } elseif (preg_match('/^\.\w+$/', $asciiName)) {
  347. // If the resulting name is only composed by the file extension
  348. $asciiName = md5($file) . $asciiName;
  349. }
  350. return $asciiName;
  351. }
  352. /**
  353. * Splits a filename into directory, base file name, extension.
  354. * If the file name starts with a dot and it's the only dot (eg: '.htaccess'), we don't consider the file to have an extension.
  355. *
  356. * @param string $filename
  357. *
  358. * @return array
  359. */
  360. public function splitFilename($filename)
  361. {
  362. $result = ['', '', ''];
  363. if (is_string($filename)) {
  364. $result[1] = $filename;
  365. $slashAt = strrpos(str_replace('\\', '/', $result[1]), '/');
  366. if ($slashAt !== false) {
  367. $result[0] = substr($result[1], 0, $slashAt + 1);
  368. $result[1] = (string) substr($result[1], $slashAt + 1);
  369. }
  370. $dotAt = strrpos($result[1], '.');
  371. if (($dotAt !== false) && ($dotAt > 0)) {
  372. $result[2] = (string) substr($result[1], $dotAt + 1);
  373. $result[1] = substr($result[1], 0, $dotAt);
  374. }
  375. }
  376. return $result;
  377. }
  378. /**
  379. * Returns the extension for a file name.
  380. *
  381. * @param string $filename
  382. *
  383. * @return string
  384. */
  385. public function getExtension($filename)
  386. {
  387. $parts = $this->splitFilename($filename);
  388. return $parts[2];
  389. }
  390. /**
  391. * Takes a path and replaces the files extension in that path with the specified extension.
  392. *
  393. * @param string $filename
  394. * @param string $extension
  395. *
  396. * @return string
  397. */
  398. public function replaceExtension($filename, $extension)
  399. {
  400. $parts = $this->splitFilename($filename);
  401. $newFilename = $parts[0] . $parts[1];
  402. if (is_string($extension) && ($extension !== '')) {
  403. $newFilename .= '.' . $extension;
  404. }
  405. return $newFilename;
  406. }
  407. /**
  408. * Checks if two path are the same, considering directory separator and OS case sensitivity.
  409. *
  410. * @param string $path1
  411. * @param string $path2
  412. *
  413. * @return bool
  414. */
  415. public function isSamePath($path1, $path2)
  416. {
  417. $path1 = str_replace(DIRECTORY_SEPARATOR, '/', $path1);
  418. $path2 = str_replace(DIRECTORY_SEPARATOR, '/', $path2);
  419. // Check if OS is case insensitive
  420. $checkFile = strtoupper(__FILE__);
  421. if ($checkFile === __FILE__) {
  422. $checkFile = strtolower(__FILE__);
  423. }
  424. if (@is_file($checkFile)) {
  425. $same = (strcasecmp($path1, $path2) === 0) ? true : false;
  426. } else {
  427. $same = ($path1 === $path2) ? true : false;
  428. }
  429. return $same;
  430. }
  431. /**
  432. * Try to set the executable bit of a file.
  433. *
  434. * @param string $file The full path
  435. * @param string $who One of 'user', 'group', 'all'
  436. *
  437. * @throws Exception Throws an exception in case of errors
  438. */
  439. public function makeExecutable($file, $who = 'all')
  440. {
  441. if (!is_file($file)) {
  442. throw new Exception(t('File %s could not be found.', $file));
  443. }
  444. $perms = @fileperms($file);
  445. if ($perms === false) {
  446. throw new Exception(t('Unable to retrieve the permissions of the file %s', $file));
  447. }
  448. $currentMode = $perms & 0777;
  449. switch ($who) {
  450. case 'user':
  451. $newMode = $currentMode | (1 << 0);
  452. break;
  453. case 'group':
  454. $newMode = $currentMode | (1 << 3);
  455. break;
  456. case 'all':
  457. $newMode = $currentMode | (1 << 6);
  458. break;
  459. default:
  460. throw new Exception(t('Bad parameter: %s', '$who'));
  461. }
  462. if ($currentMode !== $newMode) {
  463. if (@chmod($file, $newMode) === false) {
  464. throw new Exception(t('Unable to set the permissions of the file %s', $file));
  465. }
  466. }
  467. }
  468. }