vendor/contao/core-bundle/src/Resources/contao/models/FilesModel.php line 224

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\File\Metadata;
  11. use Contao\Model\Collection;
  12. use Contao\Model\Registry;
  13. use Symfony\Component\Filesystem\Path;
  14. /**
  15. * Reads and writes file entries
  16. *
  17. * The files themselves reside in the files directory. This class only handles
  18. * the corresponding database entries (database aided file system).
  19. *
  20. * @property string|integer $id
  21. * @property string|integer|null $pid
  22. * @property string|integer $tstamp
  23. * @property string|null $uuid
  24. * @property string $type
  25. * @property string $path
  26. * @property string $extension
  27. * @property string $hash
  28. * @property string|boolean $found
  29. * @property string $name
  30. * @property string|float $importantPartX
  31. * @property string|float $importantPartY
  32. * @property string|float $importantPartWidth
  33. * @property string|float $importantPartHeight
  34. * @property string|array|null $meta
  35. *
  36. * @method static FilesModel|null findByIdOrAlias($val, array $opt=array())
  37. * @method static FilesModel|null findOneBy($col, $val, array $opt=array())
  38. * @method static FilesModel|null findOneByPid($val, array $opt=array())
  39. * @method static FilesModel|null findOneByTstamp($val, array $opt=array())
  40. * @method static FilesModel|null findOneByType($val, array $opt=array())
  41. * @method static FilesModel|null findOneByExtension($val, array $opt=array())
  42. * @method static FilesModel|null findOneByHash($val, array $opt=array())
  43. * @method static FilesModel|null findOneByFound($val, array $opt=array())
  44. * @method static FilesModel|null findOneByName($val, array $opt=array())
  45. * @method static FilesModel|null findOneByImportantPartX($val, array $opt=array())
  46. * @method static FilesModel|null findOneByImportantPartY($val, array $opt=array())
  47. * @method static FilesModel|null findOneByImportantPartWidth($val, array $opt=array())
  48. * @method static FilesModel|null findOneByImportantPartHeight($val, array $opt=array())
  49. * @method static FilesModel|null findOneByMeta($val, array $opt=array())
  50. *
  51. * @method static Collection|FilesModel[]|FilesModel|null findByTstamp($val, array $opt=array())
  52. * @method static Collection|FilesModel[]|FilesModel|null findByType($val, array $opt=array())
  53. * @method static Collection|FilesModel[]|FilesModel|null findByExtension($val, array $opt=array())
  54. * @method static Collection|FilesModel[]|FilesModel|null findByHash($val, array $opt=array())
  55. * @method static Collection|FilesModel[]|FilesModel|null findByFound($val, array $opt=array())
  56. * @method static Collection|FilesModel[]|FilesModel|null findByName($val, array $opt=array())
  57. * @method static Collection|FilesModel[]|FilesModel|null findByImportantPartX($val, array $opt=array())
  58. * @method static Collection|FilesModel[]|FilesModel|null findByImportantPartY($val, array $opt=array())
  59. * @method static Collection|FilesModel[]|FilesModel|null findByImportantPartWidth($val, array $opt=array())
  60. * @method static Collection|FilesModel[]|FilesModel|null findByImportantPartHeight($val, array $opt=array())
  61. * @method static Collection|FilesModel[]|FilesModel|null findByMeta($val, array $opt=array())
  62. * @method static Collection|FilesModel[]|FilesModel|null findBy($col, $val, array $opt=array())
  63. * @method static Collection|FilesModel[]|FilesModel|null findAll(array $opt=array())
  64. *
  65. * @method static integer countById($id, array $opt=array())
  66. * @method static integer countByPid($val, array $opt=array())
  67. * @method static integer countByTstamp($val, array $opt=array())
  68. * @method static integer countByUuid($val, array $opt=array())
  69. * @method static integer countByType($val, array $opt=array())
  70. * @method static integer countByPath($val, array $opt=array())
  71. * @method static integer countByExtension($val, array $opt=array())
  72. * @method static integer countByHash($val, array $opt=array())
  73. * @method static integer countByFound($val, array $opt=array())
  74. * @method static integer countByName($val, array $opt=array())
  75. * @method static integer countByImportantPartX($val, array $opt=array())
  76. * @method static integer countByImportantPartY($val, array $opt=array())
  77. * @method static integer countByImportantPartWidth($val, array $opt=array())
  78. * @method static integer countByImportantPartHeight($val, array $opt=array())
  79. * @method static integer countByMeta($val, array $opt=array())
  80. */
  81. class FilesModel extends Model
  82. {
  83. /**
  84. * Table name
  85. * @var string
  86. */
  87. protected static $strTable = 'tl_files';
  88. /**
  89. * Returns the full absolute path.
  90. */
  91. public function getAbsolutePath(): string
  92. {
  93. $projectDir = System::getContainer()->getParameter('kernel.project_dir');
  94. return Path::makeAbsolute($this->path, $projectDir);
  95. }
  96. /**
  97. * Find a file by its primary key
  98. *
  99. * @param mixed $varValue The value
  100. * @param array $arrOptions An optional options array
  101. *
  102. * @return FilesModel|Model|null The model or null if there is no file
  103. */
  104. public static function findByPk($varValue, array $arrOptions=array())
  105. {
  106. if (static::$strPk == 'id')
  107. {
  108. return static::findById($varValue, $arrOptions);
  109. }
  110. return parent::findByPk($varValue, $arrOptions);
  111. }
  112. /**
  113. * Find a file by its ID or UUID
  114. *
  115. * @param mixed $intId The ID or UUID
  116. * @param array $arrOptions An optional options array
  117. *
  118. * @return FilesModel|null The model or null if there is no file
  119. */
  120. public static function findById($intId, array $arrOptions=array())
  121. {
  122. if (Validator::isUuid($intId))
  123. {
  124. return static::findByUuid($intId, $arrOptions);
  125. }
  126. return static::findOneBy('id', $intId, $arrOptions);
  127. }
  128. /**
  129. * Find a file by its parent ID
  130. *
  131. * @param mixed $intPid The parent ID
  132. * @param array $arrOptions An optional options array
  133. *
  134. * @return Collection|FilesModel[]|FilesModel|null A collection of models or null if there are no files
  135. */
  136. public static function findByPid($intPid, array $arrOptions=array())
  137. {
  138. $t = static::$strTable;
  139. // Convert UUIDs to binary
  140. if (Validator::isStringUuid($intPid))
  141. {
  142. $intPid = StringUtil::uuidToBin($intPid);
  143. }
  144. return static::findBy(array("$t.pid=UNHEX(?)"), bin2hex((string) $intPid), $arrOptions);
  145. }
  146. /**
  147. * Find multiple files by their IDs or UUIDs
  148. *
  149. * @param array $arrIds An array of IDs or UUIDs
  150. * @param array $arrOptions An optional options array
  151. *
  152. * @return Collection|FilesModel[]|FilesModel|null A collection of models or null if there are no files
  153. */
  154. public static function findMultipleByIds($arrIds, array $arrOptions=array())
  155. {
  156. if (empty($arrIds) || !\is_array($arrIds))
  157. {
  158. return null;
  159. }
  160. if (Validator::isUuid(current($arrIds)))
  161. {
  162. return static::findMultipleByUuids($arrIds, $arrOptions);
  163. }
  164. return parent::findMultipleByIds($arrIds, $arrOptions);
  165. }
  166. /**
  167. * Find a file by its UUID
  168. *
  169. * @param string $strUuid The UUID string
  170. * @param array $arrOptions An optional options array
  171. *
  172. * @return FilesModel|null The model or null if there is no file
  173. */
  174. public static function findByUuid($strUuid, array $arrOptions=array())
  175. {
  176. $t = static::$strTable;
  177. // Convert UUIDs to binary
  178. if (Validator::isStringUuid($strUuid))
  179. {
  180. $strUuid = StringUtil::uuidToBin($strUuid);
  181. }
  182. // Check the model registry (does not work by default due to UNHEX())
  183. if (empty($arrOptions))
  184. {
  185. /** @var FilesModel $objModel */
  186. $objModel = Registry::getInstance()->fetch(static::$strTable, $strUuid, 'uuid');
  187. if ($objModel !== null)
  188. {
  189. return $objModel;
  190. }
  191. }
  192. return static::findOneBy(array("$t.uuid=UNHEX(?)"), bin2hex((string) $strUuid), $arrOptions);
  193. }
  194. /**
  195. * Find multiple files by their UUIDs
  196. *
  197. * @param array $arrUuids An array of UUIDs
  198. * @param array $arrOptions An optional options array
  199. *
  200. * @return Collection|FilesModel[]|FilesModel|null A collection of models or null if there are no files
  201. */
  202. public static function findMultipleByUuids($arrUuids, array $arrOptions=array())
  203. {
  204. if (empty($arrUuids) || !\is_array($arrUuids))
  205. {
  206. return null;
  207. }
  208. $t = static::$strTable;
  209. foreach ($arrUuids as $k=>$v)
  210. {
  211. // Convert UUIDs to binary
  212. if (Validator::isStringUuid($v))
  213. {
  214. $v = StringUtil::uuidToBin($v);
  215. }
  216. $arrUuids[$k] = "UNHEX('" . bin2hex((string) $v) . "')";
  217. }
  218. if (!isset($arrOptions['order']))
  219. {
  220. $arrOptions['order'] = "$t.uuid!=" . implode(", $t.uuid!=", $arrUuids);
  221. }
  222. return static::findBy(array("$t.uuid IN(" . implode(",", $arrUuids) . ")"), null, $arrOptions);
  223. }
  224. /**
  225. * Find a file by its path
  226. *
  227. * @param string $path The path
  228. * @param array $arrOptions An optional options array
  229. *
  230. * @return FilesModel|null The model or null if there is no file
  231. */
  232. public static function findByPath($path, array $arrOptions=array())
  233. {
  234. if (!\is_string($path))
  235. {
  236. return null;
  237. }
  238. $projectDir = System::getContainer()->getParameter('kernel.project_dir');
  239. $uploadPath = System::getContainer()->getParameter('contao.upload_path');
  240. if (Path::isBasePath($projectDir, $path))
  241. {
  242. $path = Path::makeRelative($path, $projectDir);
  243. }
  244. if (!Path::isBasePath($uploadPath, $path))
  245. {
  246. return null;
  247. }
  248. return static::findOneBy('path', $path, $arrOptions);
  249. }
  250. /**
  251. * Find multiple files by their paths
  252. *
  253. * @param array $arrPaths An array of file paths
  254. * @param array $arrOptions An optional options array
  255. *
  256. * @return Collection|FilesModel[]|FilesModel|null A collection of models or null if there are no files
  257. */
  258. public static function findMultipleByPaths($arrPaths, array $arrOptions=array())
  259. {
  260. if (empty($arrPaths) || !\is_array($arrPaths))
  261. {
  262. return null;
  263. }
  264. $t = static::$strTable;
  265. if (!isset($arrOptions['order']))
  266. {
  267. $arrOptions['order'] = Database::getInstance()->findInSet("$t.path", $arrPaths);
  268. }
  269. return static::findBy(array("$t.path IN(" . implode(',', array_fill(0, \count($arrPaths), '?')) . ")"), $arrPaths, $arrOptions);
  270. }
  271. /**
  272. * Find multiple files with the same base path
  273. *
  274. * @param string $strPath The base path
  275. * @param array $arrOptions An optional options array
  276. *
  277. * @return Collection|FilesModel[]|FilesModel|null A collection of models or null if there are no matching files
  278. */
  279. public static function findMultipleByBasepath($strPath, array $arrOptions=array())
  280. {
  281. $t = static::$strTable;
  282. if (!isset($arrOptions['order']))
  283. {
  284. $arrOptions['order'] = "$t.path";
  285. }
  286. return static::findBy(array("$t.path LIKE ?"), $strPath . '%', $arrOptions);
  287. }
  288. /**
  289. * Find multiple files by UUID and a list of extensions
  290. *
  291. * @param array $arrUuids An array of file UUIDs
  292. * @param array $arrExtensions An array of file extensions
  293. * @param array $arrOptions An optional options array
  294. *
  295. * @return Collection|FilesModel[]|FilesModel|null A collection of models or null of there are no matching files
  296. */
  297. public static function findMultipleByUuidsAndExtensions($arrUuids, $arrExtensions, array $arrOptions=array())
  298. {
  299. if (empty($arrUuids) || empty($arrExtensions) || !\is_array($arrUuids) || !\is_array($arrExtensions))
  300. {
  301. return null;
  302. }
  303. foreach ($arrExtensions as $k=>$v)
  304. {
  305. if (!preg_match('/^[a-z0-9]{2,5}$/i', $v))
  306. {
  307. unset($arrExtensions[$k]);
  308. }
  309. }
  310. $t = static::$strTable;
  311. foreach ($arrUuids as $k=>$v)
  312. {
  313. // Convert UUIDs to binary
  314. if (Validator::isStringUuid($v))
  315. {
  316. $v = StringUtil::uuidToBin($v);
  317. }
  318. $arrUuids[$k] = "UNHEX('" . bin2hex((string) $v) . "')";
  319. }
  320. if (!isset($arrOptions['order']))
  321. {
  322. $arrOptions['order'] = "$t.uuid!=" . implode(", $t.uuid!=", $arrUuids);
  323. }
  324. return static::findBy(array("$t.uuid IN(" . implode(",", $arrUuids) . ") AND $t.extension IN('" . implode("','", $arrExtensions) . "')"), null, $arrOptions);
  325. }
  326. /**
  327. * Find all files in a folder
  328. *
  329. * @param string $strPath The folder path
  330. * @param array $arrOptions An optional options array
  331. *
  332. * @return Collection|FilesModel[]|FilesModel|null A collection of models or null if there are no matching files
  333. */
  334. public static function findMultipleFilesByFolder($strPath, array $arrOptions=array())
  335. {
  336. $t = static::$strTable;
  337. $strPath = str_replace(array('\\', '%', '_'), array('\\\\', '\\%', '\\_'), $strPath);
  338. return static::findBy(array("$t.type='file' AND $t.path LIKE ? AND $t.path NOT LIKE ?"), array($strPath . '/%', $strPath . '/%/%'), $arrOptions);
  339. }
  340. /**
  341. * Find all folders in a folder
  342. *
  343. * @param string $strPath The folder path
  344. * @param array $arrOptions An optional options array
  345. *
  346. * @return Collection|FilesModel[]|FilesModel|null A collection of models or null if there are no matching folders
  347. */
  348. public static function findMultipleFoldersByFolder($strPath, array $arrOptions=array())
  349. {
  350. $t = static::$strTable;
  351. $strPath = str_replace(array('\\', '%', '_'), array('\\\\', '\\%', '\\_'), $strPath);
  352. return static::findBy(array("$t.type='folder' AND $t.path LIKE ? AND $t.path NOT LIKE ?"), array($strPath . '/%', $strPath . '/%/%'), $arrOptions);
  353. }
  354. /**
  355. * Do not reload the data upon insert
  356. *
  357. * @param integer $intType The query type (Model::INSERT or Model::UPDATE)
  358. */
  359. protected function postSave($intType)
  360. {
  361. }
  362. /**
  363. * Return the meta fields defined in tl_files.meta.eval.metaFields
  364. */
  365. public static function getMetaFields(): array
  366. {
  367. Controller::loadDataContainer('tl_files');
  368. return array_keys($GLOBALS['TL_DCA']['tl_files']['fields']['meta']['eval']['metaFields'] ?? array());
  369. }
  370. /**
  371. * Return the metadata for this file
  372. *
  373. * Returns the metadata of the first matching locale or null if none was found.
  374. */
  375. public function getMetadata(string ...$locales): ?Metadata
  376. {
  377. $dataCollection = StringUtil::deserialize($this->meta, true);
  378. foreach ($locales as $locale)
  379. {
  380. if (!\is_array($data = $dataCollection[$locale] ?? null))
  381. {
  382. continue;
  383. }
  384. // Make sure we resolve insert tags pointing to files
  385. if (isset($data[Metadata::VALUE_URL]))
  386. {
  387. $data[Metadata::VALUE_URL] = System::getContainer()->get('contao.insert_tag.parser')->replaceInline($data[Metadata::VALUE_URL] ?? '');
  388. }
  389. // Fill missing meta fields with empty values
  390. $metaFields = self::getMetaFields();
  391. $data = array_merge(array_combine($metaFields, array_fill(0, \count($metaFields), '')), $data);
  392. return new Metadata($data);
  393. }
  394. return null;
  395. }
  396. }
  397. class_alias(FilesModel::class, 'FilesModel');