PageRenderTime 43ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/core/Filesystem.php

https://github.com/CodeYellowBV/piwik
PHP | 305 lines | 152 code | 23 blank | 130 comment | 39 complexity | 53f585a9668567c82c481a252d3ab532 MD5 | raw file
Possible License(s): LGPL-3.0, JSON, MIT, GPL-3.0, LGPL-2.1, GPL-2.0, AGPL-1.0, BSD-2-Clause, BSD-3-Clause
  1. <?php
  2. /**
  3. * Piwik - free/libre analytics platform
  4. *
  5. * @link http://piwik.org
  6. * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
  7. *
  8. */
  9. namespace Piwik;
  10. use Exception;
  11. use Piwik\Plugins\Installation\ServerFilesGenerator;
  12. use Piwik\Tracker\Cache;
  13. /**
  14. * Contains helper functions that deal with the filesystem.
  15. *
  16. */
  17. class Filesystem
  18. {
  19. /**
  20. * Called on Core install, update, plugin enable/disable
  21. * Will clear all cache that could be affected by the change in configuration being made
  22. */
  23. public static function deleteAllCacheOnUpdate($pluginName = false)
  24. {
  25. AssetManager::getInstance()->removeMergedAssets($pluginName);
  26. View::clearCompiledTemplates();
  27. Cache::deleteTrackerCache();
  28. }
  29. /**
  30. * ending WITHOUT slash
  31. *
  32. * @return string
  33. */
  34. public static function getPathToPiwikRoot()
  35. {
  36. return realpath(dirname(__FILE__) . "/..");
  37. }
  38. /**
  39. * Returns true if the string is a valid filename
  40. * File names that start with a-Z or 0-9 and contain a-Z, 0-9, underscore(_), dash(-), and dot(.) will be accepted.
  41. * File names beginning with anything but a-Z or 0-9 will be rejected (including .htaccess for example).
  42. * File names containing anything other than above mentioned will also be rejected (file names with spaces won't be accepted).
  43. *
  44. * @param string $filename
  45. * @return bool
  46. */
  47. public static function isValidFilename($filename)
  48. {
  49. return (0 !== preg_match('/(^[a-zA-Z0-9]+([a-zA-Z_0-9.-]*))$/D', $filename));
  50. }
  51. /**
  52. * Get canonicalized absolute path
  53. * See http://php.net/realpath
  54. *
  55. * @param string $path
  56. * @return string canonicalized absolute path
  57. */
  58. public static function realpath($path)
  59. {
  60. if (file_exists($path)) {
  61. return realpath($path);
  62. }
  63. return $path;
  64. }
  65. /**
  66. * Attempts to create a new directory. All errors are silenced.
  67. *
  68. * _Note: This function does **not** create directories recursively._
  69. *
  70. * @param string $path The path of the directory to create.
  71. * @api
  72. */
  73. public static function mkdir($path)
  74. {
  75. if (!is_dir($path)) {
  76. // the mode in mkdir is modified by the current umask
  77. @mkdir($path, self::getChmodForPath($path), $recursive = true);
  78. }
  79. // try to overcome restrictive umask (mis-)configuration
  80. if (!is_writable($path)) {
  81. @chmod($path, 0755);
  82. if (!is_writable($path)) {
  83. @chmod($path, 0775);
  84. // enough! we're not going to make the directory world-writeable
  85. }
  86. }
  87. }
  88. /**
  89. * Checks if the filesystem Piwik stores sessions in is NFS or not. This
  90. * check is done in order to avoid using file based sessions on NFS system,
  91. * since on such a filesystem file locking can make file based sessions
  92. * incredibly slow.
  93. *
  94. * Note: In order to figure this out, we try to run the 'df' program. If
  95. * the 'exec' or 'shell_exec' functions are not available, we can't do
  96. * the check.
  97. *
  98. * @return bool True if on an NFS filesystem, false if otherwise or if we
  99. * can't use shell_exec or exec.
  100. */
  101. public static function checkIfFileSystemIsNFS()
  102. {
  103. $sessionsPath = Session::getSessionsDirectory();
  104. // this command will display details for the filesystem that holds the $sessionsPath
  105. // path, but only if its type is NFS. if not NFS, df will return one or less lines
  106. // and the return code 1. if NFS, it will return 0 and at least 2 lines of text.
  107. $command = "df -T -t nfs \"$sessionsPath\" 2>&1";
  108. if (function_exists('exec')) // use exec
  109. {
  110. $output = $returnCode = null;
  111. @exec($command, $output, $returnCode);
  112. // check if filesystem is NFS
  113. if ($returnCode == 0
  114. && count($output) > 1
  115. ) {
  116. return true;
  117. }
  118. } else if (function_exists('shell_exec')) // use shell_exec
  119. {
  120. $output = @shell_exec($command);
  121. if ($output) {
  122. $output = explode("\n", $output);
  123. if (count($output) > 1) // check if filesystem is NFS
  124. {
  125. return true;
  126. }
  127. }
  128. }
  129. return false; // not NFS, or we can't run a program to find out
  130. }
  131. /**
  132. * Recursively find pathnames that match a pattern.
  133. *
  134. * See {@link http://php.net/manual/en/function.glob.php glob} for more info.
  135. *
  136. * @param string $sDir directory The directory to glob in.
  137. * @param string $sPattern pattern The pattern to match paths against.
  138. * @param int $nFlags `glob()` . See {@link http://php.net/manual/en/function.glob.php glob()}.
  139. * @return array The list of paths that match the pattern.
  140. * @api
  141. */
  142. public static function globr($sDir, $sPattern, $nFlags = null)
  143. {
  144. if (($aFiles = \_glob("$sDir/$sPattern", $nFlags)) == false) {
  145. $aFiles = array();
  146. }
  147. if (($aDirs = \_glob("$sDir/*", GLOB_ONLYDIR)) != false) {
  148. foreach ($aDirs as $sSubDir) {
  149. if (is_link($sSubDir)) {
  150. continue;
  151. }
  152. $aSubFiles = self::globr($sSubDir, $sPattern, $nFlags);
  153. $aFiles = array_merge($aFiles, $aSubFiles);
  154. }
  155. }
  156. return $aFiles;
  157. }
  158. /**
  159. * Recursively deletes a directory.
  160. *
  161. * @param string $dir Path of the directory to delete.
  162. * @param boolean $deleteRootToo If true, `$dir` is deleted, otherwise just its contents.
  163. * @param \Closure|false $beforeUnlink An optional closure to execute on a file path before unlinking.
  164. * @api
  165. */
  166. public static function unlinkRecursive($dir, $deleteRootToo, \Closure $beforeUnlink = null)
  167. {
  168. if (!$dh = @opendir($dir)) {
  169. return;
  170. }
  171. while (false !== ($obj = readdir($dh))) {
  172. if ($obj == '.' || $obj == '..') {
  173. continue;
  174. }
  175. $path = $dir . '/' . $obj;
  176. if ($beforeUnlink) {
  177. $beforeUnlink($path);
  178. }
  179. if (!@unlink($path)) {
  180. self::unlinkRecursive($path, true);
  181. }
  182. }
  183. closedir($dh);
  184. if ($deleteRootToo) {
  185. @rmdir($dir);
  186. }
  187. return;
  188. }
  189. /**
  190. * Copies a file from `$source` to `$dest`.
  191. *
  192. * @param string $source A path to a file, eg. './tmp/latest/index.php'. The file must exist.
  193. * @param string $dest A path to a file, eg. './index.php'. The file does not have to exist.
  194. * @param bool $excludePhp Whether to avoid copying files if the file is related to PHP
  195. * (includes .php, .tpl, .twig files).
  196. * @throws Exception If the file cannot be copied.
  197. * @return true
  198. * @api
  199. */
  200. public static function copy($source, $dest, $excludePhp = false)
  201. {
  202. static $phpExtensions = array('php', 'tpl', 'twig');
  203. if ($excludePhp) {
  204. $path_parts = pathinfo($source);
  205. if (in_array($path_parts['extension'], $phpExtensions)) {
  206. return true;
  207. }
  208. }
  209. if (!@copy($source, $dest)) {
  210. @chmod($dest, 0755);
  211. if (!@copy($source, $dest)) {
  212. $message = "Error while creating/copying file to <code>$dest</code>. <br />"
  213. . Filechecks::getErrorMessageMissingPermissions(self::getPathToPiwikRoot());
  214. throw new Exception($message);
  215. }
  216. }
  217. return true;
  218. }
  219. /**
  220. * Copies the contents of a directory recursively from `$source` to `$target`.
  221. *
  222. * @param string $source A directory or file to copy, eg. './tmp/latest'.
  223. * @param string $target A directory to copy to, eg. '.'.
  224. * @param bool $excludePhp Whether to avoid copying files if the file is related to PHP
  225. * (includes .php, .tpl, .twig files).
  226. * @throws Exception If a file cannot be copied.
  227. * @api
  228. */
  229. public static function copyRecursive($source, $target, $excludePhp = false)
  230. {
  231. if (is_dir($source)) {
  232. self::mkdir($target);
  233. $d = dir($source);
  234. while (false !== ($entry = $d->read())) {
  235. if ($entry == '.' || $entry == '..') {
  236. continue;
  237. }
  238. $sourcePath = $source . '/' . $entry;
  239. if (is_dir($sourcePath)) {
  240. self::copyRecursive($sourcePath, $target . '/' . $entry, $excludePhp);
  241. continue;
  242. }
  243. $destPath = $target . '/' . $entry;
  244. self::copy($sourcePath, $destPath, $excludePhp);
  245. }
  246. $d->close();
  247. } else {
  248. self::copy($source, $target, $excludePhp);
  249. }
  250. }
  251. /**
  252. * Deletes the given file if it exists.
  253. *
  254. * @param string $pathToFile
  255. * @return bool true in case of success or if file does not exist, false otherwise. It might fail in case the
  256. * file is not writeable.
  257. * @api
  258. */
  259. public static function deleteFileIfExists($pathToFile)
  260. {
  261. if (!file_exists($pathToFile)) {
  262. return true;
  263. }
  264. return @unlink($pathToFile);
  265. }
  266. /**
  267. * @param $path
  268. * @return int
  269. */
  270. private static function getChmodForPath($path)
  271. {
  272. $pathIsTmp = self::getPathToPiwikRoot() . '/tmp';
  273. if (strpos($path, $pathIsTmp) === 0) {
  274. // tmp/* folder
  275. return 0750;
  276. }
  277. // plugins/* and all others
  278. return 0755;
  279. }
  280. }