vendor/contao/core-bundle/src/Resources/contao/library/Contao/File.php line 169

Open in your IDE?
  1. <?php
  2. /*
  3. * This file is part of Contao.
  4. *
  5. * (c) Leo Feyer
  6. *
  7. * @license LGPL-3.0-or-later
  8. */
  9. namespace Contao;
  10. use Contao\CoreBundle\Exception\ResponseException;
  11. use Contao\Image\DeferredImageInterface;
  12. use Contao\Image\ImageDimensions;
  13. use Symfony\Component\HttpFoundation\BinaryFileResponse;
  14. use Symfony\Component\HttpFoundation\ResponseHeaderBag;
  15. use Symfony\Component\String\UnicodeString;
  16. /**
  17. * Creates, reads, writes and deletes files
  18. *
  19. * Usage:
  20. *
  21. * $file = new File('test.txt');
  22. * $file->write('This is a test');
  23. * $file->close();
  24. *
  25. * $file->delete();
  26. *
  27. * File::putContent('test.txt', 'This is a test');
  28. *
  29. * @property integer $size The file size
  30. * @property integer $filesize Alias of $size
  31. * @property string $name The file name and extension
  32. * @property string $basename Alias of $name
  33. * @property string $dirname The path of the parent folder
  34. * @property string $extension The lowercase file extension
  35. * @property string $origext The original file extension
  36. * @property string $filename The file name without extension
  37. * @property string $tmpname The name of the temporary file
  38. * @property string $path The file path
  39. * @property string $value Alias of $path
  40. * @property string $mime The mime type
  41. * @property string $hash The MD5 checksum
  42. * @property string $ctime The ctime
  43. * @property string $mtime The mtime
  44. * @property string $atime The atime
  45. * @property string $icon The mime icon name
  46. * @property string $dataUri The data URI
  47. * @property array $imageSize The file dimensions (images only)
  48. * @property integer $width The file width (images only)
  49. * @property integer $height The file height (images only)
  50. * @property array $imageViewSize The viewbox dimensions
  51. * @property integer $viewWidth The viewbox width
  52. * @property integer $viewHeight The viewbox height
  53. * @property boolean $isImage True if the file is an image
  54. * @property boolean $isGdImage True if the file can be handled by the GDlib
  55. * @property boolean $isSvgImage True if the file is an SVG image
  56. * @property integer $channels The number of channels (images only)
  57. * @property integer $bits The number of bits for each color (images only)
  58. * @property boolean $isRgbImage True if the file is an RGB image
  59. * @property boolean $isCmykImage True if the file is a CMYK image
  60. * @property resource $handle The file handle (returned by fopen())
  61. * @property string $title The file title
  62. */
  63. class File extends System
  64. {
  65. /**
  66. * File handle
  67. * @var resource
  68. */
  69. protected $resFile;
  70. /**
  71. * File name
  72. * @var string
  73. */
  74. protected $strFile;
  75. /**
  76. * Temp name
  77. * @var string
  78. */
  79. protected $strTmp;
  80. /**
  81. * Files model
  82. * @var FilesModel
  83. */
  84. protected $objModel;
  85. /**
  86. * Root dir
  87. * @var string
  88. */
  89. protected $strRootDir;
  90. /**
  91. * Pathinfo
  92. * @var array
  93. */
  94. protected $arrPathinfo = array();
  95. /**
  96. * Image size
  97. * @var array
  98. */
  99. protected $arrImageSize = array();
  100. /**
  101. * Image size runtime cache
  102. * @var array
  103. */
  104. protected static $arrImageSizeCache = array();
  105. /**
  106. * Image view size
  107. * @var array
  108. */
  109. protected $arrImageViewSize = array();
  110. /**
  111. * Instantiate a new file object
  112. *
  113. * @param string $strFile The file path
  114. *
  115. * @throws \Exception If $strFile is a directory
  116. */
  117. public function __construct($strFile)
  118. {
  119. // Handle open_basedir restrictions
  120. if ($strFile == '.')
  121. {
  122. $strFile = '';
  123. }
  124. $this->strRootDir = System::getContainer()->getParameter('kernel.project_dir');
  125. // Make sure we are not pointing to a directory
  126. if (is_dir($this->strRootDir . '/' . $strFile))
  127. {
  128. throw new \Exception(sprintf('Directory "%s" is not a file', $strFile));
  129. }
  130. $this->import(Files::class, 'Files');
  131. $this->strFile = $strFile;
  132. }
  133. /**
  134. * Close the file handle if it has not been done yet
  135. */
  136. public function __destruct()
  137. {
  138. if (\is_resource($this->resFile))
  139. {
  140. $this->Files->fclose($this->resFile);
  141. }
  142. }
  143. /**
  144. * Return an object property
  145. *
  146. * @param string $strKey The property name
  147. *
  148. * @return mixed The property value
  149. */
  150. public function __get($strKey)
  151. {
  152. switch ($strKey)
  153. {
  154. case 'size':
  155. case 'filesize':
  156. return filesize($this->strRootDir . '/' . $this->strFile);
  157. case 'name':
  158. case 'basename':
  159. if (!isset($this->arrPathinfo[$strKey]))
  160. {
  161. $this->arrPathinfo = $this->getPathinfo();
  162. }
  163. return $this->arrPathinfo['basename'];
  164. case 'dirname':
  165. case 'filename':
  166. if (!isset($this->arrPathinfo[$strKey]))
  167. {
  168. $this->arrPathinfo = $this->getPathinfo();
  169. }
  170. return $this->arrPathinfo[$strKey];
  171. case 'extension':
  172. if (!isset($this->arrPathinfo['extension']))
  173. {
  174. $this->arrPathinfo = $this->getPathinfo();
  175. }
  176. return strtolower($this->arrPathinfo['extension']);
  177. case 'origext':
  178. if (!isset($this->arrPathinfo['extension']))
  179. {
  180. $this->arrPathinfo = $this->getPathinfo();
  181. }
  182. return $this->arrPathinfo['extension'];
  183. case 'tmpname':
  184. return basename($this->strTmp);
  185. case 'path':
  186. case 'value':
  187. return $this->strFile;
  188. case 'mime':
  189. return $this->getMimeType();
  190. case 'hash':
  191. return $this->getHash();
  192. case 'ctime':
  193. return filectime($this->strRootDir . '/' . $this->strFile);
  194. case 'mtime':
  195. return filemtime($this->strRootDir . '/' . $this->strFile);
  196. case 'atime':
  197. return fileatime($this->strRootDir . '/' . $this->strFile);
  198. case 'icon':
  199. return $this->getIcon();
  200. case 'dataUri':
  201. if ($this->extension == 'svgz')
  202. {
  203. return 'data:' . $this->mime . ';base64,' . base64_encode(gzdecode($this->getContent()));
  204. }
  205. return 'data:' . $this->mime . ';base64,' . base64_encode($this->getContent());
  206. case 'imageSize':
  207. if (empty($this->arrImageSize))
  208. {
  209. $strCacheKey = $this->strFile . '|' . ($this->exists() ? $this->mtime : 0);
  210. if (isset(static::$arrImageSizeCache[$strCacheKey]))
  211. {
  212. $this->arrImageSize = static::$arrImageSizeCache[$strCacheKey];
  213. }
  214. else
  215. {
  216. $imageFactory = System::getContainer()->get('contao.image.factory');
  217. try
  218. {
  219. $dimensions = $imageFactory->create($this->strRootDir . '/' . $this->strFile)->getDimensions();
  220. if (!$dimensions->isRelative() && !$dimensions->isUndefined())
  221. {
  222. $mapper = array
  223. (
  224. 'gif' => IMAGETYPE_GIF,
  225. 'jpg' => IMAGETYPE_JPEG,
  226. 'jpeg' => IMAGETYPE_JPEG,
  227. 'png' => IMAGETYPE_PNG,
  228. 'webp' => IMAGETYPE_WEBP,
  229. 'avif' => \defined('IMAGETYPE_AVIF') ? IMAGETYPE_AVIF : 19,
  230. 'heic' => IMAGETYPE_UNKNOWN, // TODO: replace with IMAGETYPE_HEIC once available
  231. 'jxl' => IMAGETYPE_UNKNOWN, // TODO: replace with IMAGETYPE_JXL once available
  232. );
  233. $this->arrImageSize = array
  234. (
  235. $dimensions->getSize()->getWidth(),
  236. $dimensions->getSize()->getHeight(),
  237. $mapper[$this->extension] ?? 0,
  238. 'width="' . $dimensions->getSize()->getWidth() . '" height="' . $dimensions->getSize()->getHeight() . '"',
  239. 'bits' => 8,
  240. 'channels' => 3,
  241. 'mime' => $this->getMimeType()
  242. );
  243. }
  244. }
  245. catch (\Exception $e)
  246. {
  247. // ignore
  248. }
  249. }
  250. if (!isset(static::$arrImageSizeCache[$strCacheKey]))
  251. {
  252. static::$arrImageSizeCache[$strCacheKey] = $this->arrImageSize;
  253. }
  254. }
  255. return $this->arrImageSize;
  256. case 'width':
  257. return $this->imageSize[0] ?? null;
  258. case 'height':
  259. return $this->imageSize[1] ?? null;
  260. case 'imageViewSize':
  261. if (empty($this->arrImageViewSize))
  262. {
  263. if ($this->imageSize)
  264. {
  265. $this->arrImageViewSize = array
  266. (
  267. $this->imageSize[0],
  268. $this->imageSize[1]
  269. );
  270. }
  271. elseif ($this->isSvgImage)
  272. {
  273. try
  274. {
  275. $dimensions = new ImageDimensions(
  276. System::getContainer()
  277. ->get('contao.image.imagine_svg')
  278. ->open($this->strRootDir . '/' . $this->strFile)
  279. ->getSize()
  280. );
  281. $this->arrImageViewSize = array
  282. (
  283. (int) $dimensions->getSize()->getWidth(),
  284. (int) $dimensions->getSize()->getHeight()
  285. );
  286. if (!$this->arrImageViewSize[0] || !$this->arrImageViewSize[1] || $dimensions->isUndefined())
  287. {
  288. $this->arrImageViewSize = false;
  289. }
  290. }
  291. catch (\Exception $e)
  292. {
  293. $this->arrImageViewSize = false;
  294. }
  295. }
  296. }
  297. return $this->arrImageViewSize;
  298. case 'viewWidth':
  299. // Store in variable as empty() calls __isset() which is not implemented and thus always true
  300. $imageViewSize = $this->imageViewSize;
  301. return !empty($imageViewSize) ? $imageViewSize[0] : null;
  302. case 'viewHeight':
  303. // Store in variable as empty() calls __isset() which is not implemented and thus always true
  304. $imageViewSize = $this->imageViewSize;
  305. return !empty($imageViewSize) ? $imageViewSize[1] : null;
  306. case 'isImage':
  307. return $this->isGdImage || $this->isSvgImage;
  308. case 'isGdImage':
  309. return \in_array($this->extension, array('gif', 'jpg', 'jpeg', 'png', 'webp', 'avif', 'heic', 'jxl'));
  310. case 'isSvgImage':
  311. return \in_array($this->extension, array('svg', 'svgz'));
  312. case 'channels':
  313. return $this->imageSize['channels'];
  314. case 'bits':
  315. return $this->imageSize['bits'];
  316. case 'isRgbImage':
  317. return $this->channels == 3;
  318. case 'isCmykImage':
  319. return $this->channels == 4;
  320. case 'handle':
  321. if (!\is_resource($this->resFile))
  322. {
  323. $this->resFile = fopen($this->strRootDir . '/' . $this->strFile, 'r');
  324. }
  325. return $this->resFile;
  326. default:
  327. return parent::__get($strKey);
  328. }
  329. }
  330. /**
  331. * Create the file if it does not yet exist
  332. *
  333. * @throws \Exception If the file cannot be written
  334. */
  335. protected function createIfNotExists()
  336. {
  337. // The file exists
  338. if (file_exists($this->strRootDir . '/' . $this->strFile))
  339. {
  340. return;
  341. }
  342. // Handle open_basedir restrictions
  343. if (($strFolder = \dirname($this->strFile)) == '.')
  344. {
  345. $strFolder = '';
  346. }
  347. // Create the folder
  348. if (!is_dir($this->strRootDir . '/' . $strFolder))
  349. {
  350. new Folder($strFolder);
  351. }
  352. // Open the file
  353. if (!$this->resFile = $this->Files->fopen($this->strFile, 'wb'))
  354. {
  355. throw new \Exception(sprintf('Cannot create file "%s"', $this->strFile));
  356. }
  357. }
  358. /**
  359. * Check whether the file exists
  360. *
  361. * @return boolean True if the file exists
  362. */
  363. public function exists()
  364. {
  365. return file_exists($this->strRootDir . '/' . $this->strFile);
  366. }
  367. /**
  368. * Truncate the file and reset the file pointer
  369. *
  370. * @return boolean True if the operation was successful
  371. */
  372. public function truncate()
  373. {
  374. if (\is_resource($this->resFile))
  375. {
  376. ftruncate($this->resFile, 0);
  377. rewind($this->resFile);
  378. }
  379. return $this->write('');
  380. }
  381. /**
  382. * Write data to the file
  383. *
  384. * @param mixed $varData The data to be written
  385. *
  386. * @return boolean True if the operation was successful
  387. */
  388. public function write($varData)
  389. {
  390. return $this->fputs($varData, 'wb');
  391. }
  392. /**
  393. * Append data to the file
  394. *
  395. * @param mixed $varData The data to be appended
  396. * @param string $strLine The line ending (defaults to LF)
  397. *
  398. * @return boolean True if the operation was successful
  399. */
  400. public function append($varData, $strLine="\n")
  401. {
  402. return $this->fputs($varData . $strLine, 'ab');
  403. }
  404. /**
  405. * Prepend data to the file
  406. *
  407. * @param mixed $varData The data to be prepended
  408. * @param string $strLine The line ending (defaults to LF)
  409. *
  410. * @return boolean True if the operation was successful
  411. */
  412. public function prepend($varData, $strLine="\n")
  413. {
  414. return $this->fputs($varData . $strLine . $this->getContent(), 'wb');
  415. }
  416. /**
  417. * Delete the file
  418. *
  419. * @return boolean True if the operation was successful
  420. */
  421. public function delete()
  422. {
  423. $return = $this->Files->delete($this->strFile);
  424. // Update the database
  425. if (Dbafs::shouldBeSynchronized($this->strFile))
  426. {
  427. Dbafs::deleteResource($this->strFile);
  428. }
  429. return $return;
  430. }
  431. /**
  432. * Set the file permissions
  433. *
  434. * @param integer $intChmod The CHMOD settings
  435. *
  436. * @return boolean True if the operation was successful
  437. */
  438. public function chmod($intChmod)
  439. {
  440. return $this->Files->chmod($this->strFile, $intChmod);
  441. }
  442. /**
  443. * Close the file handle
  444. *
  445. * @return boolean True if the operation was successful
  446. */
  447. public function close()
  448. {
  449. if (\is_resource($this->resFile))
  450. {
  451. $this->Files->fclose($this->resFile);
  452. }
  453. // Create the file path
  454. if (!file_exists($this->strRootDir . '/' . $this->strFile))
  455. {
  456. // Handle open_basedir restrictions
  457. if (($strFolder = \dirname($this->strFile)) == '.')
  458. {
  459. $strFolder = '';
  460. }
  461. // Create the parent folder
  462. if (!is_dir($this->strRootDir . '/' . $strFolder))
  463. {
  464. new Folder($strFolder);
  465. }
  466. }
  467. // Move the temporary file to its destination
  468. $return = $this->Files->rename($this->strTmp, $this->strFile);
  469. $this->strTmp = null;
  470. // Update the database
  471. if (Dbafs::shouldBeSynchronized($this->strFile))
  472. {
  473. $this->objModel = Dbafs::addResource($this->strFile);
  474. }
  475. return $return;
  476. }
  477. /**
  478. * Return the files model
  479. *
  480. * @return FilesModel The files model
  481. */
  482. public function getModel()
  483. {
  484. if ($this->objModel === null && Dbafs::shouldBeSynchronized($this->strFile))
  485. {
  486. $this->objModel = FilesModel::findByPath($this->strFile);
  487. }
  488. return $this->objModel;
  489. }
  490. /**
  491. * Generate the image if the current file is a deferred image and does not exist yet
  492. *
  493. * @return bool True if a deferred image was resized otherwise false
  494. */
  495. public function createIfDeferred()
  496. {
  497. if (!$this->exists())
  498. {
  499. try
  500. {
  501. $image = System::getContainer()->get('contao.image.factory')->create($this->strRootDir . '/' . $this->strFile);
  502. if ($image instanceof DeferredImageInterface)
  503. {
  504. System::getContainer()->get('contao.image.legacy_resizer')->resizeDeferredImage($image);
  505. return true;
  506. }
  507. }
  508. catch (\Throwable $e)
  509. {
  510. // ignore
  511. }
  512. }
  513. return false;
  514. }
  515. /**
  516. * Return the file content as string
  517. *
  518. * @return string The file content without BOM
  519. */
  520. public function getContent()
  521. {
  522. $this->createIfDeferred();
  523. $strContent = file_get_contents($this->strRootDir . '/' . ($this->strTmp ?: $this->strFile));
  524. // Remove BOMs (see #4469)
  525. if (strncmp($strContent, "\xEF\xBB\xBF", 3) === 0)
  526. {
  527. $strContent = substr($strContent, 3);
  528. }
  529. elseif (strncmp($strContent, "\xFF\xFE", 2) === 0)
  530. {
  531. $strContent = substr($strContent, 2);
  532. }
  533. elseif (strncmp($strContent, "\xFE\xFF", 2) === 0)
  534. {
  535. $strContent = substr($strContent, 2);
  536. }
  537. return $strContent;
  538. }
  539. /**
  540. * Write to a file
  541. *
  542. * @param string $strFile Relative file name
  543. * @param string $strContent Content to be written
  544. */
  545. public static function putContent($strFile, $strContent)
  546. {
  547. $objFile = new static($strFile);
  548. $objFile->write($strContent);
  549. $objFile->close();
  550. }
  551. /**
  552. * Return the file content as array
  553. *
  554. * @return array The file content as array
  555. */
  556. public function getContentAsArray()
  557. {
  558. return array_map('rtrim', file($this->strRootDir . '/' . $this->strFile));
  559. }
  560. /**
  561. * Rename the file
  562. *
  563. * @param string $strNewName The new path
  564. *
  565. * @return boolean True if the operation was successful
  566. */
  567. public function renameTo($strNewName)
  568. {
  569. $strParent = \dirname($strNewName);
  570. // Create the parent folder if it does not exist
  571. if (!is_dir($this->strRootDir . '/' . $strParent))
  572. {
  573. new Folder($strParent);
  574. }
  575. $return = $this->Files->rename($this->strFile, $strNewName);
  576. // Update the database AFTER the file has been renamed
  577. $syncSource = Dbafs::shouldBeSynchronized($this->strFile);
  578. $syncTarget = Dbafs::shouldBeSynchronized($strNewName);
  579. // Synchronize the database
  580. if ($syncSource && $syncTarget)
  581. {
  582. $this->objModel = Dbafs::moveResource($this->strFile, $strNewName);
  583. }
  584. elseif ($syncSource)
  585. {
  586. $this->objModel = Dbafs::deleteResource($this->strFile);
  587. }
  588. elseif ($syncTarget)
  589. {
  590. $this->objModel = Dbafs::addResource($strNewName);
  591. }
  592. // Reset the object AFTER the database has been updated
  593. if ($return)
  594. {
  595. $this->strFile = $strNewName;
  596. $this->arrImageSize = array();
  597. $this->arrPathinfo = array();
  598. }
  599. return $return;
  600. }
  601. /**
  602. * Copy the file
  603. *
  604. * @param string $strNewName The target path
  605. *
  606. * @return boolean True if the operation was successful
  607. */
  608. public function copyTo($strNewName)
  609. {
  610. $strParent = \dirname($strNewName);
  611. // Create the parent folder if it does not exist
  612. if (!is_dir($this->strRootDir . '/' . $strParent))
  613. {
  614. new Folder($strParent);
  615. }
  616. $return = $this->Files->copy($this->strFile, $strNewName);
  617. // Update the database AFTER the file has been renamed
  618. $syncSource = Dbafs::shouldBeSynchronized($this->strFile);
  619. $syncTarget = Dbafs::shouldBeSynchronized($strNewName);
  620. // Synchronize the database
  621. if ($syncSource && $syncTarget)
  622. {
  623. Dbafs::copyResource($this->strFile, $strNewName);
  624. }
  625. elseif ($syncTarget)
  626. {
  627. Dbafs::addResource($strNewName);
  628. }
  629. return $return;
  630. }
  631. /**
  632. * Resize the file if it is an image
  633. *
  634. * @param integer $width The target width
  635. * @param integer $height The target height
  636. * @param string $mode The resize mode
  637. *
  638. * @return boolean True if the image could be resized successfully
  639. */
  640. public function resizeTo($width, $height, $mode='')
  641. {
  642. if (!$this->isImage)
  643. {
  644. return false;
  645. }
  646. System::getContainer()
  647. ->get('contao.image.factory')
  648. ->create($this->strRootDir . '/' . $this->strFile, array($width, $height, $mode), $this->strRootDir . '/' . $this->strFile)
  649. ;
  650. $this->arrPathinfo = array();
  651. $this->arrImageSize = array();
  652. // Clear the image size cache as mtime could potentially not change
  653. unset(static::$arrImageSizeCache[$this->strFile . '|' . $this->mtime]);
  654. return true;
  655. }
  656. /**
  657. * Send the file to the browser
  658. *
  659. * @param string $filename An optional filename
  660. * @param boolean $inline Show the file in the browser instead of opening the download dialog
  661. *
  662. * @return never
  663. *
  664. * @throws ResponseException
  665. */
  666. public function sendToBrowser($filename='', $inline=false)
  667. {
  668. $response = new BinaryFileResponse($this->strRootDir . '/' . $this->strFile);
  669. $response->setPrivate(); // public by default
  670. $response->setAutoEtag();
  671. $response->setContentDisposition
  672. (
  673. $inline ? ResponseHeaderBag::DISPOSITION_INLINE : ResponseHeaderBag::DISPOSITION_ATTACHMENT,
  674. $filename,
  675. (new UnicodeString($this->basename))->ascii()->toString()
  676. );
  677. $response->headers->addCacheControlDirective('must-revalidate');
  678. $response->headers->set('Connection', 'close');
  679. $response->headers->set('Content-Type', $this->getMimeType());
  680. throw new ResponseException($response);
  681. }
  682. /**
  683. * Check if any parent folder contains a .public file
  684. *
  685. * @return bool
  686. */
  687. public function isUnprotected()
  688. {
  689. return (new Folder(\dirname($this->strFile)))->isUnprotected();
  690. }
  691. /**
  692. * Write data to a file
  693. *
  694. * @param mixed $varData The data to be written
  695. * @param string $strMode The operation mode
  696. *
  697. * @return boolean True if the operation was successful
  698. */
  699. protected function fputs($varData, $strMode)
  700. {
  701. if (!\is_resource($this->resFile))
  702. {
  703. $this->strTmp = 'system/tmp/' . md5(uniqid(mt_rand(), true));
  704. // Copy the contents of the original file to append data
  705. if (strncmp($strMode, 'a', 1) === 0 && file_exists($this->strRootDir . '/' . $this->strFile))
  706. {
  707. $this->Files->copy($this->strFile, $this->strTmp);
  708. }
  709. // Open the temporary file
  710. if (!$this->resFile = $this->Files->fopen($this->strTmp, $strMode))
  711. {
  712. return false;
  713. }
  714. }
  715. fwrite($this->resFile, $varData);
  716. return true;
  717. }
  718. /**
  719. * Return the mime type and icon of the file based on its extension
  720. *
  721. * @return array An array with mime type and icon name
  722. */
  723. protected function getMimeInfo()
  724. {
  725. return $GLOBALS['TL_MIME'][$this->extension] ?? array('application/octet-stream', 'iconPLAIN.svg');
  726. }
  727. /**
  728. * Get the mime type of the file based on its extension
  729. *
  730. * @return string The mime type
  731. */
  732. protected function getMimeType()
  733. {
  734. $arrMime = $this->getMimeInfo();
  735. return $arrMime[0];
  736. }
  737. /**
  738. * Return the file icon depending on the file type
  739. *
  740. * @return string The icon name
  741. */
  742. protected function getIcon()
  743. {
  744. $arrMime = $this->getMimeInfo();
  745. return $arrMime[1];
  746. }
  747. /**
  748. * Return the MD5 hash of the file
  749. *
  750. * @return string The MD5 hash
  751. */
  752. protected function getHash()
  753. {
  754. return md5_file($this->strRootDir . '/' . $this->strFile);
  755. }
  756. /**
  757. * Return the path info (binary-safe)
  758. *
  759. * @return array The path info
  760. *
  761. * @see https://github.com/PHPMailer/PHPMailer/blob/master/class.phpmailer.php#L3520
  762. */
  763. protected function getPathinfo()
  764. {
  765. $matches = array();
  766. $return = array('dirname'=>'', 'basename'=>'', 'extension'=>'', 'filename'=>'');
  767. preg_match('%^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^.\\\\/]+?)|))[\\\\/.]*$%m', $this->strFile, $matches);
  768. if (isset($matches[1]))
  769. {
  770. $return['dirname'] = $this->strRootDir . '/' . $matches[1]; // see #8325
  771. }
  772. if (isset($matches[2]))
  773. {
  774. $return['basename'] = $matches[2];
  775. }
  776. if (isset($matches[5]))
  777. {
  778. $return['extension'] = $matches[5];
  779. }
  780. if (isset($matches[3]))
  781. {
  782. $return['filename'] = $matches[3];
  783. }
  784. return $return;
  785. }
  786. }
  787. class_alias(File::class, 'File');