PageRenderTime 63ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/library/Vanilla/FileUtils.php

https://github.com/vanilla/vanilla
PHP | 208 lines | 113 code | 19 blank | 76 comment | 13 complexity | 57955cef088bbaf0da9f5bbb2f451fcd MD5 | raw file
Possible License(s): GPL-2.0, MIT, AGPL-1.0
  1. <?php
  2. /**
  3. * @copyright 2009-2019 Vanilla Forums Inc.
  4. * @license GPL-2.0-only
  5. */
  6. namespace Vanilla;
  7. use Symfony\Component\Yaml\Yaml;
  8. /**
  9. * Utility functions for working with file data.
  10. */
  11. class FileUtils {
  12. /**
  13. * Check if a file was uploaded in the current request.
  14. *
  15. * @param string $filename
  16. * @return bool
  17. */
  18. public function isUploadedFile($filename) {
  19. $result = is_uploaded_file($filename);
  20. return $result;
  21. }
  22. /**
  23. * Move an upload to a new location.
  24. *
  25. * @param string $filename
  26. * @param string $destination
  27. * @return bool
  28. */
  29. public function moveUploadedFile($filename, $destination) {
  30. $result = move_uploaded_file($filename, $destination);
  31. return $result;
  32. }
  33. /**
  34. * Generate a unique path for an upload.
  35. *
  36. * @param string $extension
  37. * @param bool $chunk
  38. * @param string $name
  39. * @param string $targetDirectory
  40. * @return string
  41. */
  42. public static function generateUniqueUploadPath(
  43. string $extension,
  44. bool $chunk = true,
  45. string $name = '',
  46. string $targetDirectory = PATH_UPLOADS
  47. ) {
  48. do {
  49. $subdir = '';
  50. if (!$name) {
  51. $name = randomString(12);
  52. }
  53. if ($chunk) {
  54. $subdir = randomString(12).'/';
  55. }
  56. $path = "${targetDirectory}/{$subdir}${name}.${extension}";
  57. } while (file_exists($path));
  58. return $path;
  59. }
  60. /**
  61. * Recursively delete a directory.
  62. *
  63. * @param string $root
  64. */
  65. public static function deleteRecursively(string $root) {
  66. $files = new \RecursiveIteratorIterator(
  67. new \RecursiveDirectoryIterator($root, \RecursiveDirectoryIterator::SKIP_DOTS),
  68. \RecursiveIteratorIterator::CHILD_FIRST
  69. );
  70. foreach ($files as $fileinfo) {
  71. $deleteFunction = ($fileinfo->isDir() ? 'rmdir' : 'unlink');
  72. $deleteFunction($fileinfo->getRealPath());
  73. }
  74. // Final directory delete.
  75. rmdir($root);
  76. }
  77. /**
  78. * A version of file_put_contents() that is multi-thread safe.
  79. *
  80. * @param string $filename Path to the file where to write the data.
  81. * @param mixed $data The data to write. Can be either a string, an array or a stream resource.
  82. * @param int $mode The permissions to set on a new file.
  83. * @return boolean
  84. * @category Filesystem Functions
  85. * @see http://php.net/file_put_contents
  86. */
  87. public static function putContents($filename, $data, $mode = 0644) {
  88. $temp = tempnam(dirname($filename), 'atomic');
  89. if (!($fp = @fopen($temp, 'wb'))) {
  90. $temp = dirname($filename).DIRECTORY_SEPARATOR.uniqid('atomic');
  91. if (!($fp = @fopen($temp, 'wb'))) {
  92. trigger_error(
  93. __CLASS__ . "::" . __FUNCTION__ . "(): error writing temporary file '$temp'",
  94. E_USER_WARNING
  95. );
  96. return false;
  97. }
  98. }
  99. fwrite($fp, $data);
  100. fclose($fp);
  101. if (!@rename($temp, $filename)) {
  102. $r = @unlink($filename);
  103. $r &= @rename($temp, $filename);
  104. if (!$r) {
  105. trigger_error(
  106. __CLASS__ . "::" . __FUNCTION__ . "(): : error writing file '$filename'",
  107. E_USER_WARNING
  108. );
  109. return false;
  110. }
  111. }
  112. if (function_exists('apc_delete_file')) {
  113. // This fixes a bug with some configurations of apc.
  114. apc_delete_file($filename);
  115. } elseif (function_exists('opcache_invalidate')) {
  116. opcache_invalidate($filename);
  117. }
  118. @chmod($filename, $mode);
  119. return true;
  120. }
  121. /**
  122. * Get the contents of a file previously created using putExport.
  123. *
  124. * @param string $filename Path to the file where to read the data.
  125. * @return mixed Returns the data from the file.
  126. * @see FileUtils::putExport()
  127. */
  128. public static function getExport(string $filename) {
  129. $result = require $filename;
  130. return $result;
  131. }
  132. /**
  133. * Save a value to a file as a var_export.
  134. *
  135. * @param string $filename Path to the file where to write the data.
  136. * @param mixed $value The value to write.
  137. * @return bool
  138. */
  139. public static function putExport(string $filename, $value): bool {
  140. $data = '<?php return '.var_export($value, true).";\n";
  141. return self::putContents($filename, $data);
  142. }
  143. /**
  144. * Load and parse a file into an array based on its file extension.
  145. *
  146. * @param string $path The path to the file. The path must exist.
  147. * @return array Returns the data from the file after parsing.
  148. */
  149. public static function getArray(string $path): array {
  150. switch (pathinfo($path, PATHINFO_EXTENSION)) {
  151. case 'json':
  152. $result = json_decode(file_get_contents($path), true);
  153. break;
  154. case 'yml':
  155. case 'yaml':
  156. try {
  157. $result = Yaml::parseFile($path);
  158. } catch (\Throwable $ex) {
  159. throw new \Exception("Error parsing $path: ".$ex->getMessage(), 500, $ex);
  160. }
  161. break;
  162. default:
  163. throw new \InvalidArgumentException("Unrecognized file extension for $path", 500);
  164. }
  165. if (!is_array($result)) {
  166. throw new \Exception("Error parsing $path.", 500);
  167. }
  168. return $result;
  169. }
  170. /**
  171. * Get the data from a file or hydrate the file.
  172. *
  173. * This is a convenience method for file caching simple PHP structures using `var_export()`. It handles the logic
  174. * of whether or not to hydrate the file, but the caller otherwise must handle the hydration.
  175. *
  176. * @param string $cachePath The path to the cache file.
  177. * @param callable $hydrate The function used to hydrate the cache.
  178. * @return mixed Returns the data from the cache or the hydration function.
  179. */
  180. public static function getCached(string $cachePath, callable $hydrate) {
  181. if (!file_exists($cachePath)) {
  182. $data = $hydrate();
  183. self::putExport($cachePath, $data);
  184. return $data;
  185. } else {
  186. return self::getExport($cachePath);
  187. }
  188. }
  189. }