file.inc 42 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231
  1. <?php
  2. /**
  3. * @file
  4. * API for handling file uploads and server file management.
  5. */
  6. use Drupal\Component\Utility\Unicode;
  7. use Drupal\Component\Utility\UrlHelper;
  8. use Drupal\Component\PhpStorage\FileStorage;
  9. use Drupal\Component\Utility\Bytes;
  10. use Drupal\Core\File\FileSystem;
  11. use Drupal\Core\StreamWrapper\PublicStream;
  12. use Drupal\Core\StreamWrapper\PrivateStream;
  13. /**
  14. * Default mode for new directories. See drupal_chmod().
  15. *
  16. * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
  17. * Use \Drupal\Core\File\FileSystem::CHMOD_DIRECTORY.
  18. */
  19. const FILE_CHMOD_DIRECTORY = FileSystem::CHMOD_DIRECTORY;
  20. /**
  21. * Default mode for new files. See drupal_chmod().
  22. *
  23. * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
  24. * Use \Drupal\Core\File\FileSystem::CHMOD_FILE.
  25. */
  26. const FILE_CHMOD_FILE = FileSystem::CHMOD_FILE;
  27. /**
  28. * @defgroup file File interface
  29. * @{
  30. * Common file handling functions.
  31. */
  32. /**
  33. * Flag used by file_prepare_directory() -- create directory if not present.
  34. */
  35. const FILE_CREATE_DIRECTORY = 1;
  36. /**
  37. * Flag used by file_prepare_directory() -- file permissions may be changed.
  38. */
  39. const FILE_MODIFY_PERMISSIONS = 2;
  40. /**
  41. * Flag for dealing with existing files: Appends number until name is unique.
  42. */
  43. const FILE_EXISTS_RENAME = 0;
  44. /**
  45. * Flag for dealing with existing files: Replace the existing file.
  46. */
  47. const FILE_EXISTS_REPLACE = 1;
  48. /**
  49. * Flag for dealing with existing files: Do nothing and return FALSE.
  50. */
  51. const FILE_EXISTS_ERROR = 2;
  52. /**
  53. * Indicates that the file is permanent and should not be deleted.
  54. *
  55. * Temporary files older than the system.file.temporary_maximum_age
  56. * configuration value will be, if clean-up not disabled, removed during cron
  57. * runs, but permanent files will not be removed during the file garbage
  58. * collection process.
  59. */
  60. const FILE_STATUS_PERMANENT = 1;
  61. /**
  62. * Returns the scheme of a URI (e.g. a stream).
  63. *
  64. * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
  65. * Use \Drupal\Core\File\FileSystem::uriScheme().
  66. */
  67. function file_uri_scheme($uri) {
  68. return \Drupal::service('file_system')->uriScheme($uri);
  69. }
  70. /**
  71. * Checks that the scheme of a stream URI is valid.
  72. *
  73. * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
  74. * Use \Drupal\Core\File\FileSystem::validScheme().
  75. */
  76. function file_stream_wrapper_valid_scheme($scheme) {
  77. return \Drupal::service('file_system')->validScheme($scheme);
  78. }
  79. /**
  80. * Returns the part of a URI after the schema.
  81. *
  82. * @param string $uri
  83. * A stream, referenced as "scheme://target" or "data:target".
  84. *
  85. * @return string|bool
  86. * A string containing the target (path), or FALSE if none.
  87. * For example, the URI "public://sample/test.txt" would return
  88. * "sample/test.txt".
  89. *
  90. * @see file_uri_scheme()
  91. */
  92. function file_uri_target($uri) {
  93. // Remove the scheme from the URI and remove erroneous leading or trailing,
  94. // forward-slashes and backslashes.
  95. $target = trim(preg_replace('/^[\w\-]+:\/\/|^data:/', '', $uri), '\/');
  96. // If nothing was replaced, the URI doesn't have a valid scheme.
  97. return $target !== $uri ? $target : FALSE;
  98. }
  99. /**
  100. * Gets the default file stream implementation.
  101. *
  102. * @return string
  103. * 'public', 'private' or any other file scheme defined as the default.
  104. */
  105. function file_default_scheme() {
  106. return \Drupal::config('system.file')->get('default_scheme');
  107. }
  108. /**
  109. * Normalizes a URI by making it syntactically correct.
  110. *
  111. * A stream is referenced as "scheme://target".
  112. *
  113. * The following actions are taken:
  114. * - Remove trailing slashes from target
  115. * - Trim erroneous leading slashes from target. e.g. ":///" becomes "://".
  116. *
  117. * @param string $uri
  118. * String reference containing the URI to normalize.
  119. *
  120. * @return string
  121. * The normalized URI.
  122. */
  123. function file_stream_wrapper_uri_normalize($uri) {
  124. $scheme = \Drupal::service('file_system')->uriScheme($uri);
  125. if (file_stream_wrapper_valid_scheme($scheme)) {
  126. $target = file_uri_target($uri);
  127. if ($target !== FALSE) {
  128. $uri = $scheme . '://' . $target;
  129. }
  130. }
  131. return $uri;
  132. }
  133. /**
  134. * Creates a web-accessible URL for a stream to an external or local file.
  135. *
  136. * Compatibility: normal paths and stream wrappers.
  137. *
  138. * There are two kinds of local files:
  139. * - "managed files", i.e. those stored by a Drupal-compatible stream wrapper.
  140. * These are files that have either been uploaded by users or were generated
  141. * automatically (for example through CSS aggregation).
  142. * - "shipped files", i.e. those outside of the files directory, which ship as
  143. * part of Drupal core or contributed modules or themes.
  144. *
  145. * @param string $uri
  146. * The URI to a file for which we need an external URL, or the path to a
  147. * shipped file.
  148. *
  149. * @return string
  150. * A string containing a URL that may be used to access the file.
  151. * If the provided string already contains a preceding 'http', 'https', or
  152. * '/', nothing is done and the same string is returned. If a stream wrapper
  153. * could not be found to generate an external URL, then FALSE is returned.
  154. *
  155. * @see https://www.drupal.org/node/515192
  156. * @see file_url_transform_relative()
  157. */
  158. function file_create_url($uri) {
  159. // Allow the URI to be altered, e.g. to serve a file from a CDN or static
  160. // file server.
  161. \Drupal::moduleHandler()->alter('file_url', $uri);
  162. $scheme = \Drupal::service('file_system')->uriScheme($uri);
  163. if (!$scheme) {
  164. // Allow for:
  165. // - root-relative URIs (e.g. /foo.jpg in http://example.com/foo.jpg)
  166. // - protocol-relative URIs (e.g. //bar.jpg, which is expanded to
  167. // http://example.com/bar.jpg by the browser when viewing a page over
  168. // HTTP and to https://example.com/bar.jpg when viewing a HTTPS page)
  169. // Both types of relative URIs are characterized by a leading slash, hence
  170. // we can use a single check.
  171. if (Unicode::substr($uri, 0, 1) == '/') {
  172. return $uri;
  173. }
  174. else {
  175. // If this is not a properly formatted stream, then it is a shipped file.
  176. // Therefore, return the urlencoded URI with the base URL prepended.
  177. $options = UrlHelper::parse($uri);
  178. $path = $GLOBALS['base_url'] . '/' . UrlHelper::encodePath($options['path']);
  179. // Append the query.
  180. if ($options['query']) {
  181. $path .= '?' . UrlHelper::buildQuery($options['query']);
  182. }
  183. // Append fragment.
  184. if ($options['fragment']) {
  185. $path .= '#' . $options['fragment'];
  186. }
  187. return $path;
  188. }
  189. }
  190. elseif ($scheme == 'http' || $scheme == 'https' || $scheme == 'data') {
  191. // Check for HTTP and data URI-encoded URLs so that we don't have to
  192. // implement getExternalUrl() for the HTTP and data schemes.
  193. return $uri;
  194. }
  195. else {
  196. // Attempt to return an external URL using the appropriate wrapper.
  197. if ($wrapper = \Drupal::service('stream_wrapper_manager')->getViaUri($uri)) {
  198. return $wrapper->getExternalUrl();
  199. }
  200. else {
  201. return FALSE;
  202. }
  203. }
  204. }
  205. /**
  206. * Transforms an absolute URL of a local file to a relative URL.
  207. *
  208. * May be useful to prevent problems on multisite set-ups and prevent mixed
  209. * content errors when using HTTPS + HTTP.
  210. *
  211. * @param string $file_url
  212. * A file URL of a local file as generated by file_create_url().
  213. *
  214. * @return string
  215. * If the file URL indeed pointed to a local file and was indeed absolute,
  216. * then the transformed, relative URL to the local file. Otherwise: the
  217. * original value of $file_url.
  218. *
  219. * @see file_create_url()
  220. */
  221. function file_url_transform_relative($file_url) {
  222. // Unfortunately, we pretty much have to duplicate Symfony's
  223. // Request::getHttpHost() method because Request::getPort() may return NULL
  224. // instead of a port number.
  225. $request = \Drupal::request();
  226. $host = $request->getHost();
  227. $scheme = $request->getScheme();
  228. $port = $request->getPort() ?: 80;
  229. if (('http' == $scheme && $port == 80) || ('https' == $scheme && $port == 443)) {
  230. $http_host = $host;
  231. }
  232. else {
  233. $http_host = $host . ':' . $port;
  234. }
  235. return preg_replace('|^https?://' . $http_host . '|', '', $file_url);
  236. }
  237. /**
  238. * Checks that the directory exists and is writable.
  239. *
  240. * Directories need to have execute permissions to be considered a directory by
  241. * FTP servers, etc.
  242. *
  243. * @param $directory
  244. * A string reference containing the name of a directory path or URI. A
  245. * trailing slash will be trimmed from a path.
  246. * @param $options
  247. * A bitmask to indicate if the directory should be created if it does
  248. * not exist (FILE_CREATE_DIRECTORY) or made writable if it is read-only
  249. * (FILE_MODIFY_PERMISSIONS).
  250. *
  251. * @return
  252. * TRUE if the directory exists (or was created) and is writable. FALSE
  253. * otherwise.
  254. */
  255. function file_prepare_directory(&$directory, $options = FILE_MODIFY_PERMISSIONS) {
  256. if (!file_stream_wrapper_valid_scheme(\Drupal::service('file_system')->uriScheme($directory))) {
  257. // Only trim if we're not dealing with a stream.
  258. $directory = rtrim($directory, '/\\');
  259. }
  260. // Check if directory exists.
  261. if (!is_dir($directory)) {
  262. // Let mkdir() recursively create directories and use the default directory
  263. // permissions.
  264. if ($options & FILE_CREATE_DIRECTORY) {
  265. return @drupal_mkdir($directory, NULL, TRUE);
  266. }
  267. return FALSE;
  268. }
  269. // The directory exists, so check to see if it is writable.
  270. $writable = is_writable($directory);
  271. if (!$writable && ($options & FILE_MODIFY_PERMISSIONS)) {
  272. return drupal_chmod($directory);
  273. }
  274. return $writable;
  275. }
  276. /**
  277. * Creates a .htaccess file in each Drupal files directory if it is missing.
  278. */
  279. function file_ensure_htaccess() {
  280. file_save_htaccess('public://', FALSE);
  281. $private_path = PrivateStream::basePath();
  282. if (!empty($private_path)) {
  283. file_save_htaccess('private://', TRUE);
  284. }
  285. file_save_htaccess('temporary://', TRUE);
  286. // If a staging directory exists then it should contain a .htaccess file.
  287. // @todo https://www.drupal.org/node/2696103 catch a more specific exception
  288. // and simplify this code.
  289. try {
  290. $staging = config_get_config_directory(CONFIG_SYNC_DIRECTORY);
  291. }
  292. catch (\Exception $e) {
  293. $staging = FALSE;
  294. }
  295. if ($staging) {
  296. // Note that we log an error here if we can't write the .htaccess file. This
  297. // can occur if the staging directory is read-only. If it is then it is the
  298. // user's responsibility to create the .htaccess file.
  299. file_save_htaccess($staging, TRUE);
  300. }
  301. }
  302. /**
  303. * Creates a .htaccess file in the given directory.
  304. *
  305. * @param string $directory
  306. * The directory.
  307. * @param bool $private
  308. * (Optional) FALSE indicates that $directory should be a web-accessible
  309. * directory. Defaults to TRUE which indicates a private directory.
  310. * @param bool $force_overwrite
  311. * (Optional) Set to TRUE to attempt to overwrite the existing .htaccess file
  312. * if one is already present. Defaults to FALSE.
  313. */
  314. function file_save_htaccess($directory, $private = TRUE, $force_overwrite = FALSE) {
  315. if (\Drupal::service('file_system')->uriScheme($directory)) {
  316. $htaccess_path = file_stream_wrapper_uri_normalize($directory . '/.htaccess');
  317. }
  318. else {
  319. $directory = rtrim($directory, '/\\');
  320. $htaccess_path = $directory . '/.htaccess';
  321. }
  322. if (file_exists($htaccess_path) && !$force_overwrite) {
  323. // Short circuit if the .htaccess file already exists.
  324. return TRUE;
  325. }
  326. $htaccess_lines = FileStorage::htaccessLines($private);
  327. // Write the .htaccess file.
  328. if (file_exists($directory) && is_writable($directory) && file_put_contents($htaccess_path, $htaccess_lines)) {
  329. return drupal_chmod($htaccess_path, 0444);
  330. }
  331. else {
  332. $variables = array('%directory' => $directory, '@htaccess' => $htaccess_lines);
  333. \Drupal::logger('security')->error("Security warning: Couldn't write .htaccess file. Please create a .htaccess file in your %directory directory which contains the following lines: <pre><code>@htaccess</code></pre>", $variables);
  334. return FALSE;
  335. }
  336. }
  337. /**
  338. * Returns the standard .htaccess lines that Drupal writes to file directories.
  339. *
  340. * @param bool $private
  341. * (Optional) Set to FALSE to return the .htaccess lines for a web-accessible
  342. * public directory. The default is TRUE, which returns the .htaccess lines
  343. * for a private directory that should not be web-accessible.
  344. *
  345. * @return string
  346. * The desired contents of the .htaccess file.
  347. *
  348. * @deprecated in Drupal 8.0.x-dev and will be removed before Drupal 9.0.0.
  349. * Use \Drupal\Component\PhpStorage\FileStorage::htaccessLines().
  350. */
  351. function file_htaccess_lines($private = TRUE) {
  352. return FileStorage::htaccessLines($private);
  353. }
  354. /**
  355. * Determines whether the URI has a valid scheme for file API operations.
  356. *
  357. * There must be a scheme and it must be a Drupal-provided scheme like
  358. * 'public', 'private', 'temporary', or an extension provided with
  359. * hook_stream_wrappers().
  360. *
  361. * @param $uri
  362. * The URI to be tested.
  363. *
  364. * @return
  365. * TRUE if the URI is allowed.
  366. */
  367. function file_valid_uri($uri) {
  368. // Assert that the URI has an allowed scheme. Bare paths are not allowed.
  369. $uri_scheme = \Drupal::service('file_system')->uriScheme($uri);
  370. if (!file_stream_wrapper_valid_scheme($uri_scheme)) {
  371. return FALSE;
  372. }
  373. return TRUE;
  374. }
  375. /**
  376. * Copies a file to a new location without database changes or hook invocation.
  377. *
  378. * This is a powerful function that in many ways performs like an advanced
  379. * version of copy().
  380. * - Checks if $source and $destination are valid and readable/writable.
  381. * - If file already exists in $destination either the call will error out,
  382. * replace the file or rename the file based on the $replace parameter.
  383. * - If the $source and $destination are equal, the behavior depends on the
  384. * $replace parameter. FILE_EXISTS_REPLACE will error out. FILE_EXISTS_RENAME
  385. * will rename the file until the $destination is unique.
  386. * - Works around a PHP bug where copy() does not properly support streams if
  387. * safe_mode or open_basedir are enabled.
  388. * @see https://bugs.php.net/bug.php?id=60456
  389. *
  390. * @param $source
  391. * A string specifying the filepath or URI of the source file.
  392. * @param $destination
  393. * A URI containing the destination that $source should be copied to. The
  394. * URI may be a bare filepath (without a scheme). If this value is omitted,
  395. * Drupal's default files scheme will be used, usually "public://".
  396. * @param $replace
  397. * Replace behavior when the destination file already exists:
  398. * - FILE_EXISTS_REPLACE - Replace the existing file.
  399. * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
  400. * unique.
  401. * - FILE_EXISTS_ERROR - Do nothing and return FALSE.
  402. *
  403. * @return
  404. * The path to the new file, or FALSE in the event of an error.
  405. *
  406. * @see file_copy()
  407. */
  408. function file_unmanaged_copy($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
  409. if (!file_unmanaged_prepare($source, $destination, $replace)) {
  410. return FALSE;
  411. }
  412. // Attempt to resolve the URIs. This is necessary in certain configurations
  413. // (see above).
  414. $real_source = drupal_realpath($source) ?: $source;
  415. $real_destination = drupal_realpath($destination) ?: $destination;
  416. // Perform the copy operation.
  417. if (!@copy($real_source, $real_destination)) {
  418. \Drupal::logger('file')->error('The specified file %file could not be copied to %destination.', array('%file' => $source, '%destination' => $destination));
  419. return FALSE;
  420. }
  421. // Set the permissions on the new file.
  422. drupal_chmod($destination);
  423. return $destination;
  424. }
  425. /**
  426. * Internal function that prepares the destination for a file_unmanaged_copy or
  427. * file_unmanaged_move operation.
  428. *
  429. * - Checks if $source and $destination are valid and readable/writable.
  430. * - Checks that $source is not equal to $destination; if they are an error
  431. * is reported.
  432. * - If file already exists in $destination either the call will error out,
  433. * replace the file or rename the file based on the $replace parameter.
  434. *
  435. * @param $source
  436. * A string specifying the filepath or URI of the source file.
  437. * @param $destination
  438. * A URI containing the destination that $source should be moved/copied to.
  439. * The URI may be a bare filepath (without a scheme) and in that case the
  440. * default scheme (file://) will be used. If this value is omitted, Drupal's
  441. * default files scheme will be used, usually "public://".
  442. * @param $replace
  443. * Replace behavior when the destination file already exists:
  444. * - FILE_EXISTS_REPLACE - Replace the existing file.
  445. * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
  446. * unique.
  447. * - FILE_EXISTS_ERROR - Do nothing and return FALSE.
  448. *
  449. * @return
  450. * TRUE, or FALSE in the event of an error.
  451. *
  452. * @see file_unmanaged_copy()
  453. * @see file_unmanaged_move()
  454. */
  455. function file_unmanaged_prepare($source, &$destination = NULL, $replace = FILE_EXISTS_RENAME) {
  456. $original_source = $source;
  457. $logger = \Drupal::logger('file');
  458. // Assert that the source file actually exists.
  459. if (!file_exists($source)) {
  460. // @todo Replace drupal_set_message() calls with exceptions instead.
  461. drupal_set_message(t('The specified file %file could not be moved/copied because no file by that name exists. Please check that you supplied the correct filename.', array('%file' => $original_source)), 'error');
  462. if (($realpath = drupal_realpath($original_source)) !== FALSE) {
  463. $logger->notice('File %file (%realpath) could not be moved/copied because it does not exist.', array('%file' => $original_source, '%realpath' => $realpath));
  464. }
  465. else {
  466. $logger->notice('File %file could not be moved/copied because it does not exist.', array('%file' => $original_source));
  467. }
  468. return FALSE;
  469. }
  470. // Build a destination URI if necessary.
  471. if (!isset($destination)) {
  472. $destination = file_build_uri(drupal_basename($source));
  473. }
  474. // Prepare the destination directory.
  475. if (file_prepare_directory($destination)) {
  476. // The destination is already a directory, so append the source basename.
  477. $destination = file_stream_wrapper_uri_normalize($destination . '/' . drupal_basename($source));
  478. }
  479. else {
  480. // Perhaps $destination is a dir/file?
  481. $dirname = drupal_dirname($destination);
  482. if (!file_prepare_directory($dirname)) {
  483. // The destination is not valid.
  484. $logger->notice('File %file could not be moved/copied because the destination directory %destination is not configured correctly.', array('%file' => $original_source, '%destination' => $dirname));
  485. drupal_set_message(t('The specified file %file could not be moved/copied because the destination directory is not properly configured. This may be caused by a problem with file or directory permissions. More information is available in the system log.', array('%file' => $original_source)), 'error');
  486. return FALSE;
  487. }
  488. }
  489. // Determine whether we can perform this operation based on overwrite rules.
  490. $destination = file_destination($destination, $replace);
  491. if ($destination === FALSE) {
  492. drupal_set_message(t('The file %file could not be moved/copied because a file by that name already exists in the destination directory.', array('%file' => $original_source)), 'error');
  493. $logger->notice('File %file could not be moved/copied because a file by that name already exists in the destination directory (%destination)', array('%file' => $original_source, '%destination' => $destination));
  494. return FALSE;
  495. }
  496. // Assert that the source and destination filenames are not the same.
  497. $real_source = drupal_realpath($source);
  498. $real_destination = drupal_realpath($destination);
  499. if ($source == $destination || ($real_source !== FALSE) && ($real_source == $real_destination)) {
  500. drupal_set_message(t('The specified file %file was not moved/copied because it would overwrite itself.', array('%file' => $source)), 'error');
  501. $logger->notice('File %file could not be moved/copied because it would overwrite itself.', array('%file' => $source));
  502. return FALSE;
  503. }
  504. // Make sure the .htaccess files are present.
  505. file_ensure_htaccess();
  506. return TRUE;
  507. }
  508. /**
  509. * Constructs a URI to Drupal's default files location given a relative path.
  510. */
  511. function file_build_uri($path) {
  512. $uri = file_default_scheme() . '://' . $path;
  513. return file_stream_wrapper_uri_normalize($uri);
  514. }
  515. /**
  516. * Determines the destination path for a file.
  517. *
  518. * @param $destination
  519. * A string specifying the desired final URI or filepath.
  520. * @param $replace
  521. * Replace behavior when the destination file already exists.
  522. * - FILE_EXISTS_REPLACE - Replace the existing file.
  523. * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
  524. * unique.
  525. * - FILE_EXISTS_ERROR - Do nothing and return FALSE.
  526. *
  527. * @return
  528. * The destination filepath, or FALSE if the file already exists
  529. * and FILE_EXISTS_ERROR is specified.
  530. */
  531. function file_destination($destination, $replace) {
  532. if (file_exists($destination)) {
  533. switch ($replace) {
  534. case FILE_EXISTS_REPLACE:
  535. // Do nothing here, we want to overwrite the existing file.
  536. break;
  537. case FILE_EXISTS_RENAME:
  538. $basename = drupal_basename($destination);
  539. $directory = drupal_dirname($destination);
  540. $destination = file_create_filename($basename, $directory);
  541. break;
  542. case FILE_EXISTS_ERROR:
  543. // Error reporting handled by calling function.
  544. return FALSE;
  545. }
  546. }
  547. return $destination;
  548. }
  549. /**
  550. * Moves a file to a new location without database changes or hook invocation.
  551. *
  552. * This is a powerful function that in many ways performs like an advanced
  553. * version of rename().
  554. * - Checks if $source and $destination are valid and readable/writable.
  555. * - Checks that $source is not equal to $destination; if they are an error
  556. * is reported.
  557. * - If file already exists in $destination either the call will error out,
  558. * replace the file or rename the file based on the $replace parameter.
  559. * - Works around a PHP bug where rename() does not properly support streams if
  560. * safe_mode or open_basedir are enabled.
  561. * @see https://bugs.php.net/bug.php?id=60456
  562. *
  563. * @param $source
  564. * A string specifying the filepath or URI of the source file.
  565. * @param $destination
  566. * A URI containing the destination that $source should be moved to. The
  567. * URI may be a bare filepath (without a scheme) and in that case the default
  568. * scheme (file://) will be used. If this value is omitted, Drupal's default
  569. * files scheme will be used, usually "public://".
  570. * @param $replace
  571. * Replace behavior when the destination file already exists:
  572. * - FILE_EXISTS_REPLACE - Replace the existing file.
  573. * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
  574. * unique.
  575. * - FILE_EXISTS_ERROR - Do nothing and return FALSE.
  576. *
  577. * @return
  578. * The path to the new file, or FALSE in the event of an error.
  579. *
  580. * @see file_move()
  581. */
  582. function file_unmanaged_move($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
  583. if (!file_unmanaged_prepare($source, $destination, $replace)) {
  584. return FALSE;
  585. }
  586. // Ensure compatibility with Windows.
  587. // @see drupal_unlink()
  588. if ((substr(PHP_OS, 0, 3) == 'WIN') && (!file_stream_wrapper_valid_scheme(file_uri_scheme($source)))) {
  589. chmod($source, 0600);
  590. }
  591. // Attempt to resolve the URIs. This is necessary in certain configurations
  592. // (see above) and can also permit fast moves across local schemes.
  593. $real_source = drupal_realpath($source) ?: $source;
  594. $real_destination = drupal_realpath($destination) ?: $destination;
  595. // Perform the move operation.
  596. if (!@rename($real_source, $real_destination)) {
  597. // Fall back to slow copy and unlink procedure. This is necessary for
  598. // renames across schemes that are not local, or where rename() has not been
  599. // implemented. It's not necessary to use drupal_unlink() as the Windows
  600. // issue has already been resolved above.
  601. if (!@copy($real_source, $real_destination) || !@unlink($real_source)) {
  602. \Drupal::logger('file')->error('The specified file %file could not be moved to %destination.', array('%file' => $source, '%destination' => $destination));
  603. return FALSE;
  604. }
  605. }
  606. // Set the permissions on the new file.
  607. drupal_chmod($destination);
  608. return $destination;
  609. }
  610. /**
  611. * Modifies a filename as needed for security purposes.
  612. *
  613. * Munging a file name prevents unknown file extensions from masking exploit
  614. * files. When web servers such as Apache decide how to process a URL request,
  615. * they use the file extension. If the extension is not recognized, Apache
  616. * skips that extension and uses the previous file extension. For example, if
  617. * the file being requested is exploit.php.pps, and Apache does not recognize
  618. * the '.pps' extension, it treats the file as PHP and executes it. To make
  619. * this file name safe for Apache and prevent it from executing as PHP, the
  620. * .php extension is "munged" into .php_, making the safe file name
  621. * exploit.php_.pps.
  622. *
  623. * Specifically, this function adds an underscore to all extensions that are
  624. * between 2 and 5 characters in length, internal to the file name, and not
  625. * included in $extensions.
  626. *
  627. * Function behavior is also controlled by the configuration
  628. * 'system.file:allow_insecure_uploads'. If it evaluates to TRUE, no alterations
  629. * will be made, if it evaluates to FALSE, the filename is 'munged'. *
  630. * @param $filename
  631. * File name to modify.
  632. * @param $extensions
  633. * A space-separated list of extensions that should not be altered.
  634. * @param $alerts
  635. * If TRUE, drupal_set_message() will be called to display a message if the
  636. * file name was changed.
  637. *
  638. * @return string
  639. * The potentially modified $filename.
  640. */
  641. function file_munge_filename($filename, $extensions, $alerts = TRUE) {
  642. $original = $filename;
  643. // Allow potentially insecure uploads for very savvy users and admin
  644. if (!\Drupal::config('system.file')->get('allow_insecure_uploads')) {
  645. // Remove any null bytes. See
  646. // http://php.net/manual/security.filesystem.nullbytes.php
  647. $filename = str_replace(chr(0), '', $filename);
  648. $whitelist = array_unique(explode(' ', strtolower(trim($extensions))));
  649. // Split the filename up by periods. The first part becomes the basename
  650. // the last part the final extension.
  651. $filename_parts = explode('.', $filename);
  652. $new_filename = array_shift($filename_parts); // Remove file basename.
  653. $final_extension = array_pop($filename_parts); // Remove final extension.
  654. // Loop through the middle parts of the name and add an underscore to the
  655. // end of each section that could be a file extension but isn't in the list
  656. // of allowed extensions.
  657. foreach ($filename_parts as $filename_part) {
  658. $new_filename .= '.' . $filename_part;
  659. if (!in_array(strtolower($filename_part), $whitelist) && preg_match("/^[a-zA-Z]{2,5}\d?$/", $filename_part)) {
  660. $new_filename .= '_';
  661. }
  662. }
  663. $filename = $new_filename . '.' . $final_extension;
  664. if ($alerts && $original != $filename) {
  665. drupal_set_message(t('For security reasons, your upload has been renamed to %filename.', array('%filename' => $filename)));
  666. }
  667. }
  668. return $filename;
  669. }
  670. /**
  671. * Undoes the effect of file_munge_filename().
  672. *
  673. * @param $filename
  674. * String with the filename to be unmunged.
  675. *
  676. * @return
  677. * An unmunged filename string.
  678. */
  679. function file_unmunge_filename($filename) {
  680. return str_replace('_.', '.', $filename);
  681. }
  682. /**
  683. * Creates a full file path from a directory and filename.
  684. *
  685. * If a file with the specified name already exists, an alternative will be
  686. * used.
  687. *
  688. * @param $basename
  689. * String filename
  690. * @param $directory
  691. * String containing the directory or parent URI.
  692. *
  693. * @return
  694. * File path consisting of $directory and a unique filename based off
  695. * of $basename.
  696. */
  697. function file_create_filename($basename, $directory) {
  698. // Strip control characters (ASCII value < 32). Though these are allowed in
  699. // some filesystems, not many applications handle them well.
  700. $basename = preg_replace('/[\x00-\x1F]/u', '_', $basename);
  701. if (substr(PHP_OS, 0, 3) == 'WIN') {
  702. // These characters are not allowed in Windows filenames
  703. $basename = str_replace(array(':', '*', '?', '"', '<', '>', '|'), '_', $basename);
  704. }
  705. // A URI or path may already have a trailing slash or look like "public://".
  706. if (substr($directory, -1) == '/') {
  707. $separator = '';
  708. }
  709. else {
  710. $separator = '/';
  711. }
  712. $destination = $directory . $separator . $basename;
  713. if (file_exists($destination)) {
  714. // Destination file already exists, generate an alternative.
  715. $pos = strrpos($basename, '.');
  716. if ($pos !== FALSE) {
  717. $name = substr($basename, 0, $pos);
  718. $ext = substr($basename, $pos);
  719. }
  720. else {
  721. $name = $basename;
  722. $ext = '';
  723. }
  724. $counter = 0;
  725. do {
  726. $destination = $directory . $separator . $name . '_' . $counter++ . $ext;
  727. } while (file_exists($destination));
  728. }
  729. return $destination;
  730. }
  731. /**
  732. * Deletes a file and its database record.
  733. *
  734. * Instead of directly deleting a file, it is strongly recommended to delete
  735. * file usages instead. That will automatically mark the file as temporary and
  736. * remove it during cleanup.
  737. *
  738. * @param $fid
  739. * The file id.
  740. *
  741. * @see file_unmanaged_delete()
  742. * @see \Drupal\file\FileUsage\FileUsageBase::delete()
  743. */
  744. function file_delete($fid) {
  745. return file_delete_multiple(array($fid));
  746. }
  747. /**
  748. * Deletes files.
  749. *
  750. * Instead of directly deleting a file, it is strongly recommended to delete
  751. * file usages instead. That will automatically mark the file as temporary and
  752. * remove it during cleanup.
  753. *
  754. * @param $fid
  755. * The file id.
  756. *
  757. * @see file_unmanaged_delete()
  758. * @see \Drupal\file\FileUsage\FileUsageBase::delete()
  759. */
  760. function file_delete_multiple(array $fids) {
  761. entity_delete_multiple('file', $fids);
  762. }
  763. /**
  764. * Deletes a file without database changes or hook invocations.
  765. *
  766. * This function should be used when the file to be deleted does not have an
  767. * entry recorded in the files table.
  768. *
  769. * @param $path
  770. * A string containing a file path or (streamwrapper) URI.
  771. *
  772. * @return
  773. * TRUE for success or path does not exist, or FALSE in the event of an
  774. * error.
  775. *
  776. * @see file_delete()
  777. * @see file_unmanaged_delete_recursive()
  778. */
  779. function file_unmanaged_delete($path) {
  780. if (is_file($path)) {
  781. return drupal_unlink($path);
  782. }
  783. $logger = \Drupal::logger('file');
  784. if (is_dir($path)) {
  785. $logger->error('%path is a directory and cannot be removed using file_unmanaged_delete().', array('%path' => $path));
  786. return FALSE;
  787. }
  788. // Return TRUE for non-existent file, but log that nothing was actually
  789. // deleted, as the current state is the intended result.
  790. if (!file_exists($path)) {
  791. $logger->notice('The file %path was not deleted because it does not exist.', array('%path' => $path));
  792. return TRUE;
  793. }
  794. // We cannot handle anything other than files and directories. Log an error
  795. // for everything else (sockets, symbolic links, etc).
  796. $logger->error('The file %path is not of a recognized type so it was not deleted.', array('%path' => $path));
  797. return FALSE;
  798. }
  799. /**
  800. * Deletes all files and directories in the specified filepath recursively.
  801. *
  802. * If the specified path is a directory then the function will call itself
  803. * recursively to process the contents. Once the contents have been removed the
  804. * directory will also be removed.
  805. *
  806. * If the specified path is a file then it will be passed to
  807. * file_unmanaged_delete().
  808. *
  809. * Note that this only deletes visible files with write permission.
  810. *
  811. * @param $path
  812. * A string containing either an URI or a file or directory path.
  813. * @param $callback
  814. * (optional) Callback function to run on each file prior to deleting it and
  815. * on each directory prior to traversing it. For example, can be used to
  816. * modify permissions.
  817. *
  818. * @return
  819. * TRUE for success or if path does not exist, FALSE in the event of an
  820. * error.
  821. *
  822. * @see file_unmanaged_delete()
  823. */
  824. function file_unmanaged_delete_recursive($path, $callback = NULL) {
  825. if (isset($callback)) {
  826. call_user_func($callback, $path);
  827. }
  828. if (is_dir($path)) {
  829. $dir = dir($path);
  830. while (($entry = $dir->read()) !== FALSE) {
  831. if ($entry == '.' || $entry == '..') {
  832. continue;
  833. }
  834. $entry_path = $path . '/' . $entry;
  835. file_unmanaged_delete_recursive($entry_path, $callback);
  836. }
  837. $dir->close();
  838. return drupal_rmdir($path);
  839. }
  840. return file_unmanaged_delete($path);
  841. }
  842. /**
  843. * Moves an uploaded file to a new location.
  844. *
  845. * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
  846. * Use \Drupal\Core\File\FileSystem::moveUploadedFile().
  847. */
  848. function drupal_move_uploaded_file($filename, $uri) {
  849. return \Drupal::service('file_system')->moveUploadedFile($filename, $uri);
  850. }
  851. /**
  852. * Saves a file to the specified destination without invoking file API.
  853. *
  854. * This function is identical to file_save_data() except the file will not be
  855. * saved to the {file_managed} table and none of the file_* hooks will be
  856. * called.
  857. *
  858. * @param $data
  859. * A string containing the contents of the file.
  860. * @param $destination
  861. * A string containing the destination location. This must be a stream wrapper
  862. * URI. If no value is provided, a randomized name will be generated and the
  863. * file will be saved using Drupal's default files scheme, usually
  864. * "public://".
  865. * @param $replace
  866. * Replace behavior when the destination file already exists:
  867. * - FILE_EXISTS_REPLACE - Replace the existing file.
  868. * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
  869. * unique.
  870. * - FILE_EXISTS_ERROR - Do nothing and return FALSE.
  871. *
  872. * @return
  873. * A string with the path of the resulting file, or FALSE on error.
  874. *
  875. * @see file_save_data()
  876. */
  877. function file_unmanaged_save_data($data, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
  878. // Write the data to a temporary file.
  879. $temp_name = drupal_tempnam('temporary://', 'file');
  880. if (file_put_contents($temp_name, $data) === FALSE) {
  881. drupal_set_message(t('The file could not be created.'), 'error');
  882. return FALSE;
  883. }
  884. // Move the file to its final destination.
  885. return file_unmanaged_move($temp_name, $destination, $replace);
  886. }
  887. /**
  888. * Finds all files that match a given mask in a given directory.
  889. *
  890. * Directories and files beginning with a dot are excluded; this prevents
  891. * hidden files and directories (such as SVN working directories) from being
  892. * scanned. Use the umask option to skip configuration directories to
  893. * eliminate the possibility of accidentally exposing configuration
  894. * information. Also, you can use the base directory, recurse, and min_depth
  895. * options to improve performance by limiting how much of the filesystem has
  896. * to be traversed.
  897. *
  898. * @param $dir
  899. * The base directory or URI to scan, without trailing slash.
  900. * @param $mask
  901. * The preg_match() regular expression for files to be included.
  902. * @param $options
  903. * An associative array of additional options, with the following elements:
  904. * - 'nomask': The preg_match() regular expression for files to be excluded.
  905. * There is no default.
  906. * - 'callback': The callback function to call for each match. There is no
  907. * default callback.
  908. * - 'recurse': When TRUE, the directory scan will recurse the entire tree
  909. * starting at the provided directory. Defaults to TRUE.
  910. * - 'key': The key to be used for the returned associative array of files.
  911. * Possible values are 'uri', for the file's URI; 'filename', for the
  912. * basename of the file; and 'name' for the name of the file without the
  913. * extension. Defaults to 'uri'.
  914. * - 'min_depth': Minimum depth of directories to return files from. Defaults
  915. * to 0.
  916. * @param $depth
  917. * The current depth of recursion. This parameter is only used internally and
  918. * should not be passed in.
  919. *
  920. * @return
  921. * An associative array (keyed on the chosen key) of objects with 'uri',
  922. * 'filename', and 'name' properties corresponding to the matched files.
  923. */
  924. function file_scan_directory($dir, $mask, $options = array(), $depth = 0) {
  925. // Merge in defaults.
  926. $options += array(
  927. 'callback' => 0,
  928. 'recurse' => TRUE,
  929. 'key' => 'uri',
  930. 'min_depth' => 0,
  931. );
  932. // Normalize $dir only once.
  933. if ($depth == 0) {
  934. $dir = file_stream_wrapper_uri_normalize($dir);
  935. $dir_has_slash = (substr($dir, -1) === '/');
  936. }
  937. $options['key'] = in_array($options['key'], array('uri', 'filename', 'name')) ? $options['key'] : 'uri';
  938. $files = array();
  939. // Avoid warnings when opendir does not have the permissions to open a
  940. // directory.
  941. if (is_dir($dir)) {
  942. if ($handle = @opendir($dir)) {
  943. while (FALSE !== ($filename = readdir($handle))) {
  944. // Skip this file if it matches the nomask or starts with a dot.
  945. if ($filename[0] != '.' && !(isset($options['nomask']) && preg_match($options['nomask'], $filename))) {
  946. if ($depth == 0 && $dir_has_slash) {
  947. $uri = "$dir$filename";
  948. }
  949. else {
  950. $uri = "$dir/$filename";
  951. }
  952. if ($options['recurse'] && is_dir($uri)) {
  953. // Give priority to files in this folder by merging them in after
  954. // any subdirectory files.
  955. $files = array_merge(file_scan_directory($uri, $mask, $options, $depth + 1), $files);
  956. }
  957. elseif ($depth >= $options['min_depth'] && preg_match($mask, $filename)) {
  958. // Always use this match over anything already set in $files with
  959. // the same $options['key'].
  960. $file = new stdClass();
  961. $file->uri = $uri;
  962. $file->filename = $filename;
  963. $file->name = pathinfo($filename, PATHINFO_FILENAME);
  964. $key = $options['key'];
  965. $files[$file->$key] = $file;
  966. if ($options['callback']) {
  967. $options['callback']($uri);
  968. }
  969. }
  970. }
  971. }
  972. closedir($handle);
  973. }
  974. else {
  975. \Drupal::logger('file')->error('@dir can not be opened', array('@dir' => $dir));
  976. }
  977. }
  978. return $files;
  979. }
  980. /**
  981. * Determines the maximum file upload size by querying the PHP settings.
  982. *
  983. * @return
  984. * A file size limit in bytes based on the PHP upload_max_filesize and
  985. * post_max_size
  986. */
  987. function file_upload_max_size() {
  988. static $max_size = -1;
  989. if ($max_size < 0) {
  990. // Start with post_max_size.
  991. $max_size = Bytes::toInt(ini_get('post_max_size'));
  992. // If upload_max_size is less, then reduce. Except if upload_max_size is
  993. // zero, which indicates no limit.
  994. $upload_max = Bytes::toInt(ini_get('upload_max_filesize'));
  995. if ($upload_max > 0 && $upload_max < $max_size) {
  996. $max_size = $upload_max;
  997. }
  998. }
  999. return $max_size;
  1000. }
  1001. /**
  1002. * Sets the permissions on a file or directory.
  1003. *
  1004. * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
  1005. * Use \Drupal\Core\File\FileSystem::chmod().
  1006. */
  1007. function drupal_chmod($uri, $mode = NULL) {
  1008. return \Drupal::service('file_system')->chmod($uri, $mode);
  1009. }
  1010. /**
  1011. * Deletes a file.
  1012. *
  1013. * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
  1014. * Use \Drupal\Core\File\FileSystem::unlink().
  1015. */
  1016. function drupal_unlink($uri, $context = NULL) {
  1017. return \Drupal::service('file_system')->unlink($uri, $context);
  1018. }
  1019. /**
  1020. * Resolves the absolute filepath of a local URI or filepath.
  1021. *
  1022. * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
  1023. * Use \Drupal\Core\File\FileSystem::realpath().
  1024. */
  1025. function drupal_realpath($uri) {
  1026. return \Drupal::service('file_system')->realpath($uri);
  1027. }
  1028. /**
  1029. * Gets the name of the directory from a given path.
  1030. *
  1031. * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
  1032. * Use \Drupal\Core\File\FileSystem::dirname().
  1033. */
  1034. function drupal_dirname($uri) {
  1035. return \Drupal::service('file_system')->dirname($uri);
  1036. }
  1037. /**
  1038. * Gets the filename from a given path.
  1039. *
  1040. * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
  1041. * Use \Drupal\Core\File\FileSystem::basename().
  1042. */
  1043. function drupal_basename($uri, $suffix = NULL) {
  1044. return \Drupal::service('file_system')->basename($uri, $suffix);
  1045. }
  1046. /**
  1047. * Creates a directory, optionally creating missing components in the path to
  1048. * the directory.
  1049. *
  1050. * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
  1051. * Use \Drupal\Core\File\FileSystem::mkdir().
  1052. */
  1053. function drupal_mkdir($uri, $mode = NULL, $recursive = FALSE, $context = NULL) {
  1054. return \Drupal::service('file_system')->mkdir($uri, $mode, $recursive, $context);
  1055. }
  1056. /**
  1057. * Removes a directory.
  1058. *
  1059. * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
  1060. * Use \Drupal\Core\File\FileSystem::rmdir().
  1061. */
  1062. function drupal_rmdir($uri, $context = NULL) {
  1063. return \Drupal::service('file_system')->rmdir($uri, $context);
  1064. }
  1065. /**
  1066. * Creates a file with a unique filename in the specified directory.
  1067. *
  1068. * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
  1069. * Use \Drupal\Core\File\FileSystem::tempnam().
  1070. */
  1071. function drupal_tempnam($directory, $prefix) {
  1072. return \Drupal::service('file_system')->tempnam($directory, $prefix);
  1073. }
  1074. /**
  1075. * Gets and sets the path of the configured temporary directory.
  1076. *
  1077. * @return mixed|null
  1078. * A string containing the path to the temporary directory.
  1079. */
  1080. function file_directory_temp() {
  1081. $temporary_directory = \Drupal::config('system.file')->get('path.temporary');
  1082. if (empty($temporary_directory)) {
  1083. // Needs set up.
  1084. $config = \Drupal::configFactory()->getEditable('system.file');
  1085. $temporary_directory = file_directory_os_temp();
  1086. if (empty($temporary_directory)) {
  1087. // If no directory has been found default to 'files/tmp'.
  1088. $temporary_directory = PublicStream::basePath() . '/tmp';
  1089. // Windows accepts paths with either slash (/) or backslash (\), but will
  1090. // not accept a path which contains both a slash and a backslash. Since
  1091. // the 'file_public_path' variable may have either format, we sanitize
  1092. // everything to use slash which is supported on all platforms.
  1093. $temporary_directory = str_replace('\\', '/', $temporary_directory);
  1094. }
  1095. // Save the path of the discovered directory. Do not check config schema on
  1096. // save.
  1097. $config->set('path.temporary', (string) $temporary_directory)->save(TRUE);
  1098. }
  1099. return $temporary_directory;
  1100. }
  1101. /**
  1102. * Discovers a writable system-appropriate temporary directory.
  1103. *
  1104. * @return mixed
  1105. * A string containing the path to the temporary directory.
  1106. */
  1107. function file_directory_os_temp() {
  1108. $directories = array();
  1109. // Has PHP been set with an upload_tmp_dir?
  1110. if (ini_get('upload_tmp_dir')) {
  1111. $directories[] = ini_get('upload_tmp_dir');
  1112. }
  1113. // Operating system specific dirs.
  1114. if (substr(PHP_OS, 0, 3) == 'WIN') {
  1115. $directories[] = 'c:\\windows\\temp';
  1116. $directories[] = 'c:\\winnt\\temp';
  1117. }
  1118. else {
  1119. $directories[] = '/tmp';
  1120. }
  1121. // PHP may be able to find an alternative tmp directory.
  1122. $directories[] = sys_get_temp_dir();
  1123. foreach ($directories as $directory) {
  1124. if (is_dir($directory) && is_writable($directory)) {
  1125. return $directory;
  1126. }
  1127. }
  1128. return FALSE;
  1129. }
  1130. /**
  1131. * @} End of "defgroup file".
  1132. */