vendor/contao/core-bundle/src/Resources/contao/library/Contao/Dbafs.php line 68

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\Filter\SyncExclude;
  11. use Contao\Model\Collection;
  12. use Symfony\Component\Filesystem\Path;
  13. /**
  14.  * Handles the database assisted file system (DBAFS)
  15.  *
  16.  * The class provides static methods to add, move, copy and delete resources as
  17.  * well as a method to synchronize the file system and the database.
  18.  *
  19.  * Usage:
  20.  *
  21.  *     $file = Dbafs::addResource('files/james-wilson.jpg');
  22.  */
  23. class Dbafs
  24. {
  25.     /**
  26.      * Synchronize the database
  27.      * @var array
  28.      */
  29.     protected static $arrShouldBeSynchronized = array();
  30.     /**
  31.      * Adds a file or folder with its parent folders
  32.      *
  33.      * @param string  $strResource      The path to the file or folder
  34.      * @param boolean $blnUpdateFolders If true, the parent folders will be updated
  35.      *
  36.      * @return FilesModel The files model
  37.      *
  38.      * @throws \Exception                If a parent ID entry is missing
  39.      * @throws \InvalidArgumentException If the resource is outside the upload folder
  40.      */
  41.     public static function addResource($strResource$blnUpdateFolders=true)
  42.     {
  43.         self::validateUtf8Path($strResource);
  44.         $uploadPath Path::normalize(System::getContainer()->getParameter('contao.upload_path'));
  45.         $projectDir Path::normalize(System::getContainer()->getParameter('kernel.project_dir'));
  46.         // Remove trailing slashes (see #5707)
  47.         if (substr($strResource, -1) == '/')
  48.         {
  49.             $strResource substr($strResource0, -1);
  50.         }
  51.         // Normalize the path (see #6034)
  52.         $strResource str_replace(array('\\''//'), '/'$strResource);
  53.         // The resource does not exist or lies outside the upload directory
  54.         if (!$strResource || !file_exists($projectDir '/' $strResource) || !Path::isBasePath($uploadPath$strResource))
  55.         {
  56.             throw new \InvalidArgumentException("Invalid resource $strResource");
  57.         }
  58.         $objModel FilesModel::findByPath($strResource);
  59.         // Return the model if it exists already
  60.         if ($objModel !== null)
  61.         {
  62.             $objFile = ($objModel->type == 'folder') ? new Folder($objModel->path) : new File($objModel->path);
  63.             // Update the timestamp and file hash (see #4818, #7828)
  64.             if ($objModel->hash != $objFile->hash)
  65.             {
  66.                 $objModel->tstamp time();
  67.                 $objModel->hash   $objFile->hash;
  68.                 $objModel->save();
  69.             }
  70.             return $objModel;
  71.         }
  72.         $arrPaths    = array();
  73.         $arrChunks   array_filter(explode('/'Path::makeRelative($strResource$uploadPath)), 'strlen');
  74.         $strPath     $uploadPath;
  75.         $arrPids     = array($strPath => null);
  76.         $arrUpdate   = array($strResource);
  77.         $objDatabase Database::getInstance();
  78.         // Build the paths
  79.         while (\count($arrChunks))
  80.         {
  81.             $strPath .= '/' array_shift($arrChunks);
  82.             $arrPaths[] = $strPath;
  83.         }
  84.         unset($arrChunks);
  85.         $objModels FilesModel::findMultipleByPaths($arrPaths);
  86.         // Unset the entries in $arrPaths if the DB entry exists
  87.         if ($objModels !== null)
  88.         {
  89.             while ($objModels->next())
  90.             {
  91.                 if (($i array_search($objModels->path$arrPaths)) !== false)
  92.                 {
  93.                     unset($arrPaths[$i]);
  94.                     $arrPids[$objModels->path] = $objModels->uuid;
  95.                 }
  96.             }
  97.         }
  98.         $arrPaths array_values($arrPaths);
  99.         // If the resource is a folder, also add its contents
  100.         if (is_dir($projectDir '/' $strResource))
  101.         {
  102.             /** @var \SplFileInfo[] $objFiles */
  103.             $objFiles = new \RecursiveIteratorIterator(
  104.                 new SyncExclude(
  105.                     new \RecursiveDirectoryIterator(
  106.                         $projectDir '/' $strResource,
  107.                         \FilesystemIterator::UNIX_PATHS|\FilesystemIterator::FOLLOW_SYMLINKS|\FilesystemIterator::SKIP_DOTS
  108.                     )
  109.                 ),
  110.                 \RecursiveIteratorIterator::SELF_FIRST
  111.             );
  112.             // Add the relative path
  113.             foreach ($objFiles as $objFile)
  114.             {
  115.                 $strRelpath StringUtil::stripRootDir($objFile->getPathname());
  116.                 if ($objFile->isDir())
  117.                 {
  118.                     $arrUpdate[] = $strRelpath;
  119.                 }
  120.                 $arrPaths[] = $strRelpath;
  121.             }
  122.         }
  123.         $objReturn null;
  124.         // Create the new resources
  125.         foreach ($arrPaths as $strPath)
  126.         {
  127.             if (\in_array(basename($strPath), array('.public''.nosync')))
  128.             {
  129.                 continue;
  130.             }
  131.             $strParent \dirname($strPath);
  132.             // The parent ID should be in $arrPids
  133.             // Do not use isset() here, because the PID can be null
  134.             if (\array_key_exists($strParent$arrPids))
  135.             {
  136.                 $strPid $arrPids[$strParent];
  137.             }
  138.             else
  139.             {
  140.                 throw new \Exception("No parent entry for $strParent");
  141.             }
  142.             // Create the file or folder
  143.             if (is_file($projectDir '/' $strPath))
  144.             {
  145.                 $objFile = new File($strPath);
  146.                 $objModel = new FilesModel();
  147.                 $objModel->pid       $strPid;
  148.                 $objModel->tstamp    time();
  149.                 $objModel->name      $objFile->name;
  150.                 $objModel->type      'file';
  151.                 $objModel->path      $objFile->path;
  152.                 $objModel->extension $objFile->extension;
  153.                 $objModel->hash      $objFile->hash;
  154.                 $objModel->uuid      $objDatabase->getUuid();
  155.                 $objModel->save();
  156.                 $arrPids[$objFile->path] = $objModel->uuid;
  157.             }
  158.             else
  159.             {
  160.                 $objFolder = new Folder($strPath);
  161.                 $objModel = new FilesModel();
  162.                 $objModel->pid       $strPid;
  163.                 $objModel->tstamp    time();
  164.                 $objModel->name      $objFolder->name;
  165.                 $objModel->type      'folder';
  166.                 $objModel->path      $objFolder->path;
  167.                 $objModel->extension '';
  168.                 $objModel->uuid      $objDatabase->getUuid();
  169.                 $objModel->save();
  170.                 $arrPids[$objFolder->path] = $objModel->uuid;
  171.             }
  172.             // Store the model to be returned (see #5979)
  173.             if ($objModel->path == $strResource)
  174.             {
  175.                 $objReturn $objModel;
  176.             }
  177.         }
  178.         // Update the folder hashes from bottom up after all file hashes are set
  179.         foreach (array_reverse($arrPaths) as $strPath)
  180.         {
  181.             if (is_dir($projectDir '/' $strPath))
  182.             {
  183.                 $objModel FilesModel::findByPath($strPath);
  184.                 $objModel->hash = static::getFolderHash($strPath);
  185.                 $objModel->save();
  186.             }
  187.         }
  188.         // Update the folder hashes
  189.         if ($blnUpdateFolders)
  190.         {
  191.             static::updateFolderHashes($arrUpdate);
  192.         }
  193.         return $objReturn;
  194.     }
  195.     /**
  196.      * Moves a file or folder to a new location
  197.      *
  198.      * @param string $strSource      The source path
  199.      * @param string $strDestination The target path
  200.      *
  201.      * @return FilesModel The files model
  202.      */
  203.     public static function moveResource($strSource$strDestination)
  204.     {
  205.         self::validateUtf8Path($strSource);
  206.         self::validateUtf8Path($strDestination);
  207.         $objFile FilesModel::findByPath($strSource);
  208.         // If there is no entry, directly add the destination
  209.         if ($objFile === null)
  210.         {
  211.             $objFile = static::addResource($strDestination);
  212.         }
  213.         $strFolder \dirname($strDestination);
  214.         // Set the new parent ID
  215.         if ($strFolder == System::getContainer()->getParameter('contao.upload_path'))
  216.         {
  217.             $objFile->pid null;
  218.         }
  219.         else
  220.         {
  221.             $objFolder FilesModel::findByPath($strFolder);
  222.             if ($objFolder === null)
  223.             {
  224.                 $objFolder = static::addResource($strFolder);
  225.             }
  226.             $objFile->pid $objFolder->uuid;
  227.         }
  228.         // Save the resource
  229.         $objFile->path $strDestination;
  230.         $objFile->name basename($strDestination);
  231.         $objFile->save();
  232.         // Update all child records
  233.         if ($objFile->type == 'folder')
  234.         {
  235.             $objFiles FilesModel::findMultipleByBasepath($strSource '/');
  236.             if ($objFiles !== null)
  237.             {
  238.                 while ($objFiles->next())
  239.                 {
  240.                     $objFiles->path preg_replace('@^' preg_quote($strSource'@') . '/@'$strDestination '/'$objFiles->path);
  241.                     $objFiles->save();
  242.                 }
  243.             }
  244.         }
  245.         // Update the MD5 hash of the parent folders
  246.         if (($strPath \dirname($strSource)) != System::getContainer()->getParameter('contao.upload_path'))
  247.         {
  248.             static::updateFolderHashes($strPath);
  249.         }
  250.         if (($strPath \dirname($strDestination)) != System::getContainer()->getParameter('contao.upload_path'))
  251.         {
  252.             static::updateFolderHashes($strPath);
  253.         }
  254.         return $objFile;
  255.     }
  256.     /**
  257.      * Copies a file or folder to a new location
  258.      *
  259.      * @param string $strSource      The source path
  260.      * @param string $strDestination The target path
  261.      *
  262.      * @return FilesModel The files model
  263.      */
  264.     public static function copyResource($strSource$strDestination)
  265.     {
  266.         self::validateUtf8Path($strSource);
  267.         self::validateUtf8Path($strDestination);
  268.         $objDatabase Database::getInstance();
  269.         $objFile FilesModel::findByPath($strSource);
  270.         // Add the source entry
  271.         if ($objFile === null)
  272.         {
  273.             $objFile = static::addResource($strSource);
  274.         }
  275.         $strFolder \dirname($strDestination);
  276.         /** @var FilesModel $objNewFile */
  277.         $objNewFile = clone $objFile->current();
  278.         // Set the new parent ID
  279.         if ($strFolder == System::getContainer()->getParameter('contao.upload_path'))
  280.         {
  281.             $objNewFile->pid null;
  282.         }
  283.         else
  284.         {
  285.             $objFolder FilesModel::findByPath($strFolder);
  286.             if ($objFolder === null)
  287.             {
  288.                 $objFolder = static::addResource($strFolder);
  289.             }
  290.             $objNewFile->pid $objFolder->uuid;
  291.         }
  292.         // Save the resource
  293.         $objNewFile->tstamp time();
  294.         $objNewFile->uuid   $objDatabase->getUuid();
  295.         $objNewFile->path   $strDestination;
  296.         $objNewFile->name   basename($strDestination);
  297.         $objNewFile->save();
  298.         // Update all child records
  299.         if ($objFile->type == 'folder')
  300.         {
  301.             $objFiles FilesModel::findMultipleByBasepath($strSource '/');
  302.             if ($objFiles !== null)
  303.             {
  304.                 while ($objFiles->next())
  305.                 {
  306.                     /** @var FilesModel $objNew */
  307.                     $objNew = clone $objFiles->current();
  308.                     $objNew->pid    $objNewFile->uuid;
  309.                     $objNew->tstamp time();
  310.                     $objNew->uuid   $objDatabase->getUuid();
  311.                     $objNew->path   str_replace($strSource '/'$strDestination '/'$objFiles->path);
  312.                     $objNew->save();
  313.                 }
  314.             }
  315.         }
  316.         // Update the MD5 hash of the parent folders
  317.         if (($strPath \dirname($strSource)) != System::getContainer()->getParameter('contao.upload_path'))
  318.         {
  319.             static::updateFolderHashes($strPath);
  320.         }
  321.         if (($strPath \dirname($strDestination)) != System::getContainer()->getParameter('contao.upload_path'))
  322.         {
  323.             static::updateFolderHashes($strPath);
  324.         }
  325.         return $objNewFile;
  326.     }
  327.     /**
  328.      * Removes a file or folder
  329.      *
  330.      * @param string $strResource The path to the file or folder
  331.      */
  332.     public static function deleteResource($strResource)
  333.     {
  334.         self::validateUtf8Path($strResource);
  335.         $objModel FilesModel::findByPath($strResource);
  336.         // Remove the resource
  337.         if ($objModel !== null)
  338.         {
  339.             $objModel->delete();
  340.         }
  341.         // Look for subfolders and files
  342.         $objFiles FilesModel::findMultipleByBasepath($strResource '/');
  343.         // Remove subfolders and files as well
  344.         if ($objFiles !== null)
  345.         {
  346.             while ($objFiles->next())
  347.             {
  348.                 $objFiles->delete();
  349.             }
  350.         }
  351.         static::updateFolderHashes(\dirname($strResource));
  352.         return null;
  353.     }
  354.     /**
  355.      * Update the hashes of all parent folders of a resource
  356.      *
  357.      * @param mixed $varResource A path or an array of paths to update
  358.      */
  359.     public static function updateFolderHashes($varResource)
  360.     {
  361.         $arrPaths  = array();
  362.         if (!\is_array($varResource))
  363.         {
  364.             $varResource = array($varResource);
  365.         }
  366.         $projectDir Path::normalize(System::getContainer()->getParameter('kernel.project_dir'));
  367.         $uploadPath Path::normalize(System::getContainer()->getParameter('contao.upload_path'));
  368.         foreach ($varResource as $strResource)
  369.         {
  370.             self::validateUtf8Path($strResource);
  371.             $strResource Path::normalize($strResource);
  372.             $arrChunks   array_filter(explode('/'Path::makeRelative($strResource$uploadPath)), 'strlen');
  373.             $strPath     $uploadPath;
  374.             // Do not check files
  375.             if (is_file($projectDir '/' $strResource))
  376.             {
  377.                 array_pop($arrChunks);
  378.             }
  379.             // Build the paths
  380.             while (\count($arrChunks))
  381.             {
  382.                 $strPath .= '/' array_shift($arrChunks);
  383.                 $arrPaths[] = $strPath;
  384.             }
  385.             unset($arrChunks);
  386.         }
  387.         $arrPaths array_values(array_unique($arrPaths));
  388.         // Store the hash of each folder
  389.         foreach (array_reverse($arrPaths) as $strPath)
  390.         {
  391.             $objModel  FilesModel::findByPath($strPath);
  392.             // The DB entry does not yet exist
  393.             if ($objModel === null)
  394.             {
  395.                 $objModel = static::addResource($strPathfalse);
  396.             }
  397.             $objModel->hash = static::getFolderHash($strPath);
  398.             $objModel->save();
  399.         }
  400.     }
  401.     /**
  402.      * Synchronize the file system with the database
  403.      *
  404.      * @return string The path to the synchronization log file
  405.      *
  406.      * @throws \Exception If a parent ID entry is missing
  407.      */
  408.     public static function syncFiles()
  409.     {
  410.         @ini_set('max_execution_time'0);
  411.         // Consider the suhosin.memory_limit (see #7035)
  412.         if (\extension_loaded('suhosin'))
  413.         {
  414.             if (($limit \ini_get('suhosin.memory_limit')) !== '')
  415.             {
  416.                 @ini_set('memory_limit'$limit);
  417.             }
  418.         }
  419.         else
  420.         {
  421.             @ini_set('memory_limit', -1);
  422.         }
  423.         $objDatabase Database::getInstance();
  424.         // Begin atomic database access
  425.         $objDatabase->lockTables(array('tl_files'=>'WRITE'));
  426.         $objDatabase->beginTransaction();
  427.         // Reset the "found" flag
  428.         $objDatabase->executeStatement("UPDATE tl_files SET found=''");
  429.         $projectDir System::getContainer()->getParameter('kernel.project_dir');
  430.         /** @var \SplFileInfo[] $objFiles */
  431.         $objFiles = new \RecursiveIteratorIterator(
  432.             new SyncExclude(
  433.                 new \RecursiveDirectoryIterator(
  434.                     $projectDir '/' System::getContainer()->getParameter('contao.upload_path'),
  435.                     \FilesystemIterator::UNIX_PATHS|\FilesystemIterator::FOLLOW_SYMLINKS|\FilesystemIterator::SKIP_DOTS
  436.                 )
  437.             ),
  438.             \RecursiveIteratorIterator::SELF_FIRST
  439.         );
  440.         $strLog 'system/tmp/' md5(uniqid(mt_rand(), true));
  441.         // Open the log file
  442.         $objLog = new File($strLog);
  443.         $objLog->truncate();
  444.         $arrModels = array();
  445.         $arrFoldersToHash = array();
  446.         $arrFoldersToCompare = array();
  447.         // Create or update the database entries
  448.         foreach ($objFiles as $objFile)
  449.         {
  450.             $strRelpath StringUtil::stripRootDir($objFile->getPathname());
  451.             if (preg_match('//u'$strRelpath) !== 1)
  452.             {
  453.                 $objLog->append("[Malformed UTF-8 filename] $strRelpath");
  454.                 continue;
  455.             }
  456.             // Get all subfiles in a single query
  457.             if ($objFile->isDir())
  458.             {
  459.                 $objSubfiles FilesModel::findMultipleFilesByFolder($strRelpath);
  460.                 if ($objSubfiles !== null)
  461.                 {
  462.                     while ($objSubfiles->next())
  463.                     {
  464.                         $arrModels[$objSubfiles->path] = $objSubfiles->current();
  465.                     }
  466.                 }
  467.             }
  468.             /** @var Model $objModel */
  469.             $objModel $arrModels[$strRelpath] ?? FilesModel::findByPath($strRelpath);
  470.             if ($objModel === null)
  471.             {
  472.                 // Add a log entry
  473.                 $objLog->append("[Added] $strRelpath");
  474.                 // Get the parent folder
  475.                 $strParent \dirname($strRelpath);
  476.                 // Get the parent ID
  477.                 if ($strParent == System::getContainer()->getParameter('contao.upload_path'))
  478.                 {
  479.                     $strPid null;
  480.                 }
  481.                 else
  482.                 {
  483.                     $objParent FilesModel::findByPath($strParent);
  484.                     if ($objParent === null)
  485.                     {
  486.                         throw new \Exception("No parent entry for $strParent");
  487.                     }
  488.                     $strPid $objParent->uuid;
  489.                 }
  490.                 // Create the file or folder
  491.                 if (is_file($projectDir '/' $strRelpath))
  492.                 {
  493.                     $objFile = new File($strRelpath);
  494.                     $objModel = new FilesModel();
  495.                     $objModel->pid       $strPid;
  496.                     $objModel->tstamp    time();
  497.                     $objModel->name      $objFile->name;
  498.                     $objModel->type      'file';
  499.                     $objModel->path      $objFile->path;
  500.                     $objModel->extension $objFile->extension;
  501.                     $objModel->found     2;
  502.                     $objModel->hash      $objFile->hash;
  503.                     $objModel->uuid      $objDatabase->getUuid();
  504.                     $objModel->save();
  505.                 }
  506.                 else
  507.                 {
  508.                     $objFolder = new Folder($strRelpath);
  509.                     $objModel = new FilesModel();
  510.                     $objModel->pid       $strPid;
  511.                     $objModel->tstamp    time();
  512.                     $objModel->name      $objFolder->name;
  513.                     $objModel->type      'folder';
  514.                     $objModel->path      $objFolder->path;
  515.                     $objModel->extension '';
  516.                     $objModel->found     2;
  517.                     $objModel->uuid      $objDatabase->getUuid();
  518.                     $objModel->save();
  519.                     $arrFoldersToHash[] = $strRelpath;
  520.                 }
  521.             }
  522.             elseif ($objFile->isDir())
  523.             {
  524.                 $arrFoldersToCompare[] = $objModel;
  525.             }
  526.             else
  527.             {
  528.                 // Check whether the MD5 hash has changed
  529.                 $strHash = (new File($strRelpath))->hash;
  530.                 $strType = ($objModel->hash != $strHash) ? 'Changed' 'Unchanged';
  531.                 // Add a log entry
  532.                 $objLog->append("[$strType$strRelpath");
  533.                 // Update the record
  534.                 $objModel->found 1;
  535.                 $objModel->hash  $strHash;
  536.                 $objModel->save();
  537.             }
  538.         }
  539.         // Update the folder hashes from bottom up after all file hashes are set
  540.         foreach (array_reverse($arrFoldersToHash) as $strPath)
  541.         {
  542.             $objModel FilesModel::findByPath($strPath);
  543.             $objModel->hash = static::getFolderHash($strPath);
  544.             $objModel->save();
  545.         }
  546.         // Compare the folders after all hashes are set
  547.         foreach (array_reverse($arrFoldersToCompare) as $objModel)
  548.         {
  549.             // Check whether the MD5 hash has changed
  550.             $strHash = static::getFolderHash($objModel->path);
  551.             $strType = ($objModel->hash != $strHash) ? 'Changed' 'Unchanged';
  552.             // Add a log entry
  553.             $objLog->append("[$strType$objModel->path");
  554.             // Update the record
  555.             $objModel->found 1;
  556.             $objModel->hash  $strHash;
  557.             $objModel->save();
  558.         }
  559.         // Check for left-over entries in the DB
  560.         $objFiles FilesModel::findByFound('');
  561.         if ($objFiles !== null)
  562.         {
  563.             $arrMapped = array();
  564.             $arrPidUpdate = array();
  565.             /** @var Collection|FilesModel $objFiles */
  566.             while ($objFiles->next())
  567.             {
  568.                 $objFound FilesModel::findBy(array('hash=?''found=2'), $objFiles->hash);
  569.                 if ($objFound !== null)
  570.                 {
  571.                     // Check for matching file names if the result is ambiguous (see #5644)
  572.                     if ($objFound->count() > 1)
  573.                     {
  574.                         while ($objFound->next())
  575.                         {
  576.                             if ($objFound->name == $objFiles->name)
  577.                             {
  578.                                 $objFound $objFound->current();
  579.                                 break;
  580.                             }
  581.                         }
  582.                     }
  583.                     // If another file has been mapped already, delete the entry (see #6008)
  584.                     if (\in_array($objFound->path$arrMapped))
  585.                     {
  586.                         $objLog->append("[Deleted] $objFiles->path");
  587.                         $objFiles->delete();
  588.                         continue;
  589.                     }
  590.                     $arrMapped[] = $objFound->path;
  591.                     // Store the PID change
  592.                     if ($objFiles->type == 'folder')
  593.                     {
  594.                         $arrPidUpdate[$objFound->uuid] = $objFiles->uuid;
  595.                     }
  596.                     // Add a log entry BEFORE changing the object
  597.                     $objLog->append("[Moved] $objFiles->path to $objFound->path");
  598.                     // Update the original entry
  599.                     $objFiles->pid    $objFound->pid;
  600.                     $objFiles->tstamp $objFound->tstamp;
  601.                     $objFiles->name   $objFound->name;
  602.                     $objFiles->type   $objFound->type;
  603.                     $objFiles->path   $objFound->path;
  604.                     $objFiles->found  1;
  605.                     // Delete the newer (duplicate) entry
  606.                     $objFound->delete();
  607.                     // Then save the modified original entry (prevents duplicate key errors)
  608.                     $objFiles->save();
  609.                 }
  610.                 else
  611.                 {
  612.                     // Add a log entry BEFORE changing the object
  613.                     $objLog->append("[Deleted] $objFiles->path");
  614.                     // Delete the entry if the resource has gone
  615.                     $objFiles->delete();
  616.                 }
  617.             }
  618.             // Update the PID of the child records
  619.             if (!empty($arrPidUpdate))
  620.             {
  621.                 foreach ($arrPidUpdate as $from=>$to)
  622.                 {
  623.                     $objChildren FilesModel::findByPid($from);
  624.                     if ($objChildren !== null)
  625.                     {
  626.                         while ($objChildren->next())
  627.                         {
  628.                             $objChildren->pid $to;
  629.                             $objChildren->save();
  630.                         }
  631.                     }
  632.                 }
  633.             }
  634.         }
  635.         // Close the log file
  636.         $objLog->close();
  637.         // Reset the found flag
  638.         $objDatabase->executeStatement("UPDATE tl_files SET found=1 WHERE found=2");
  639.         // Finalize database access
  640.         $objDatabase->commitTransaction();
  641.         $objDatabase->unlockTables();
  642.         // Return the path to the log file
  643.         return $strLog;
  644.     }
  645.     /**
  646.      * Get the folder hash from the database by combining the hashes of all children
  647.      *
  648.      * @param string $strPath The relative path
  649.      *
  650.      * @return string MD5 hash
  651.      */
  652.     public static function getFolderHash($strPath)
  653.     {
  654.         self::validateUtf8Path($strPath);
  655.         $strPath str_replace(array('\\''%''_'), array('\\\\''\\%''\\_'), $strPath);
  656.         $arrHash = array();
  657.         $objChildren Database::getInstance()
  658.             ->prepare("SELECT hash, name FROM tl_files WHERE path LIKE ? AND path NOT LIKE ? ORDER BY name")
  659.             ->execute($strPath '/%'$strPath '/%/%')
  660.         ;
  661.         if ($objChildren !== null)
  662.         {
  663.             while ($objChildren->next())
  664.             {
  665.                 $arrHash[] = $objChildren->hash $objChildren->name;
  666.             }
  667.         }
  668.         return md5(implode("\0"$arrHash));
  669.     }
  670.     /**
  671.      * Check if the current resource should be synchronized with the database
  672.      *
  673.      * @param string $strPath The relative path
  674.      *
  675.      * @return bool True if the current resource needs to be synchronized with the database
  676.      */
  677.     public static function shouldBeSynchronized($strPath)
  678.     {
  679.         if (!isset(static::$arrShouldBeSynchronized[$strPath]) || !\is_bool(static::$arrShouldBeSynchronized[$strPath]))
  680.         {
  681.             static::$arrShouldBeSynchronized[$strPath] = !static::isFileSyncExclude($strPath);
  682.         }
  683.         return static::$arrShouldBeSynchronized[$strPath];
  684.     }
  685.     /**
  686.      * Check if a file or folder is excluded from synchronization
  687.      *
  688.      * @param string $strPath The relative path
  689.      *
  690.      * @return bool True if the file or folder is excluded from synchronization
  691.      */
  692.     protected static function isFileSyncExclude($strPath)
  693.     {
  694.         self::validateUtf8Path($strPath);
  695.         $projectDir System::getContainer()->getParameter('kernel.project_dir');
  696.         // Look for an existing parent folder (see #410)
  697.         while ($strPath != '.' && !is_dir($projectDir '/' $strPath))
  698.         {
  699.             $strPath \dirname($strPath);
  700.         }
  701.         if ($strPath == '.')
  702.         {
  703.             return true;
  704.         }
  705.         $uploadPath System::getContainer()->getParameter('contao.upload_path');
  706.         // Outside the files directory
  707.         if (!Path::isBasePath($uploadPath$strPath))
  708.         {
  709.             return true;
  710.         }
  711.         return (new Folder($strPath))->isUnsynchronized();
  712.     }
  713.     private static function validateUtf8Path($strPath)
  714.     {
  715.         if (preg_match('//u'$strPath) !== 1)
  716.         {
  717.             throw new \InvalidArgumentException(sprintf('Path "%s" contains malformed UTF-8 characters.'$strPath));
  718.         }
  719.     }
  720. }
  721. class_alias(Dbafs::class, 'Dbafs');