core-bundle/src/Resources/contao/library/Contao/Dbafs.php
public static function syncFiles()
	{
		@ini_set('max_execution_time', 0);

		// Consider the suhosin.memory_limit (see #7035)
		if (\extension_loaded('suhosin'))
		{
			if (($limit = ini_get('suhosin.memory_limit')) !== '')
			{
				@ini_set('memory_limit', $limit);
			}
		}
		else
		{
			@ini_set('memory_limit', -1);
		}

		$objDatabase = Database::getInstance();

		// Lock the files table
		$objDatabase->lockTables(array('tl_files'=>'WRITE'));

		// Reset the "found" flag
		$objDatabase->query("UPDATE tl_files SET found=''");

		$rootDir = System::getContainer()->getParameter('kernel.project_dir');

		/** @var \SplFileInfo[] $objFiles */
		$objFiles = new \RecursiveIteratorIterator(
			new SyncExclude(
				new \RecursiveDirectoryIterator(
					$rootDir . '/' . Config::get('uploadPath'),
					\FilesystemIterator::UNIX_PATHS|\FilesystemIterator::FOLLOW_SYMLINKS|\FilesystemIterator::SKIP_DOTS
				)
			), \RecursiveIteratorIterator::SELF_FIRST
		);

		$strLog = 'system/tmp/' . md5(uniqid(mt_rand(), true));

		// Open the log file
		$objLog = new File($strLog);
		$objLog->truncate();

		$arrModels = array();
		$arrFoldersToHash = array();
		$arrFoldersToCompare = array();

		// Create or update the database entries
		foreach ($objFiles as $objFile)
		{
			$strRelpath = StringUtil::stripRootDir($objFile->getPathname());

			// Get all subfiles in a single query
			if ($objFile->isDir())
			{
				$objSubfiles = FilesModel::findMultipleFilesByFolder($strRelpath);

				if ($objSubfiles !== null)
				{
					while ($objSubfiles->next())
					{
						$arrModels[$objSubfiles->path] = $objSubfiles->current();
					}
				}
			}

			// Get the model
			if (isset($arrModels[$strRelpath]))
			{
				/** @var Model $objModel */
				$objModel = $arrModels[$strRelpath];
			}
			else
			{
				$objModel = FilesModel::findByPath($strRelpath);
			}

			if ($objModel === null)
			{
				// Add a log entry
				$objLog->append("[Added] $strRelpath");

				// Get the parent folder
				$strParent = \dirname($strRelpath);

				// Get the parent ID
				if ($strParent == Config::get('uploadPath'))
				{
					$strPid = null;
				}
				else
				{
					$objParent = FilesModel::findByPath($strParent);

					if ($objParent === null)
					{
						throw new \Exception("No parent entry for $strParent");
					}

					$strPid = $objParent->uuid;
				}

				// Create the file or folder
				if (is_file($rootDir . '/' . $strRelpath))
				{
					$objFile = new File($strRelpath);

					$objModel = new FilesModel();
					$objModel->pid       = $strPid;
					$objModel->tstamp    = time();
					$objModel->name      = $objFile->name;
					$objModel->type      = 'file';
					$objModel->path      = $objFile->path;
					$objModel->extension = $objFile->extension;
					$objModel->found     = 2;
					$objModel->hash      = $objFile->hash;
					$objModel->uuid      = $objDatabase->getUuid();
					$objModel->save();
				}
				else
				{
					$objFolder = new Folder($strRelpath);

					$objModel = new FilesModel();
					$objModel->pid       = $strPid;
					$objModel->tstamp    = time();
					$objModel->name      = $objFolder->name;
					$objModel->type      = 'folder';
					$objModel->path      = $objFolder->path;
					$objModel->extension = '';
					$objModel->found     = 2;
					$objModel->uuid      = $objDatabase->getUuid();
					$objModel->save();

					$arrFoldersToHash[] = $strRelpath;
				}
			}
			else
			{
				if ($objFile->isDir())
				{
					$arrFoldersToCompare[] = $objModel;
				}
				else
				{
					// Check whether the MD5 hash has changed
					$strHash = (new File($strRelpath))->hash;
					$strType = ($objModel->hash != $strHash) ? 'Changed' : 'Unchanged';

					// Add a log entry
					$objLog->append("[$strType] $strRelpath");

					// Update the record
					$objModel->found = 1;
					$objModel->hash  = $strHash;
					$objModel->save();
				}
			}
		}

		// Update the folder hashes from bottom up after all file hashes are set
		foreach (array_reverse($arrFoldersToHash) as $strPath)
		{
			$objModel = FilesModel::findByPath($strPath);
			$objModel->hash = static::getFolderHash($strPath);
			$objModel->save();
		}

		// Compare the folders after all hashes are set
		foreach (array_reverse($arrFoldersToCompare) as $objModel)
		{
			// Check whether the MD5 hash has changed
			$strHash = static::getFolderHash($objModel->path);
			$strType = ($objModel->hash != $strHash) ? 'Changed' : 'Unchanged';

			// Add a log entry
			$objLog->append("[$strType] {$objModel->path}");

			// Update the record
			$objModel->found = 1;
			$objModel->hash  = $strHash;
			$objModel->save();
		}

		// Check for left-over entries in the DB
		$objFiles = FilesModel::findByFound('');

		if ($objFiles !== null)
		{
			$arrMapped = array();
			$arrPidUpdate = array();

			/** @var Collection|FilesModel $objFiles */
			while ($objFiles->next())
			{
				$objFound = FilesModel::findBy(array('hash=?', 'found=2'), $objFiles->hash);

				if ($objFound !== null)
				{
					// Check for matching file names if the result is ambiguous (see #5644)
					if ($objFound->count() > 1)
					{
						while ($objFound->next())
						{
							if ($objFound->name == $objFiles->name)
							{
								$objFound = $objFound->current();
								break;
							}
						}
					}

					// If another file has been mapped already, delete the entry (see #6008)
					if (\in_array($objFound->path, $arrMapped))
					{
						$objLog->append("[Deleted] {$objFiles->path}");
						$objFiles->delete();
						continue;
					}

					$arrMapped[] = $objFound->path;

					// Store the PID change
					if ($objFiles->type == 'folder')
					{
						$arrPidUpdate[$objFound->uuid] = $objFiles->uuid;
					}

					// Add a log entry BEFORE changing the object
					$objLog->append("[Moved] {$objFiles->path} to {$objFound->path}");

					// Update the original entry
					$objFiles->pid    = $objFound->pid;
					$objFiles->tstamp = $objFound->tstamp;
					$objFiles->name   = $objFound->name;
					$objFiles->type   = $objFound->type;
					$objFiles->path   = $objFound->path;
					$objFiles->found  = 1;

					// Delete the newer (duplicate) entry
					$objFound->delete();

					// Then save the modified original entry (prevents duplicate key errors)
					$objFiles->save();
				}
				else
				{
					// Add a log entry BEFORE changing the object
					$objLog->append("[Deleted] {$objFiles->path}");

					// Delete the entry if the resource has gone
					$objFiles->delete();
				}
			}

			// Update the PID of the child records
			if (!empty($arrPidUpdate))
			{
				foreach ($arrPidUpdate as $from=>$to)
				{
					$objChildren = FilesModel::findByPid($from);

					if ($objChildren !== null)
					{
						while ($objChildren->next())
						{
							$objChildren->pid = $to;
							$objChildren->save();
						}
					}
				}
			}
		}

		// Close the log file
		$objLog->close();

		// Reset the found flag
		$objDatabase->query("UPDATE tl_files SET found=1 WHERE found=2");

		// Unlock the tables
		$objDatabase->unlockTables();

		// Return the path to the log file
		return $strLog;
	}
public static function syncFiles()
	{
		@ini_set('max_execution_time', 0);

		// Consider the suhosin.memory_limit (see #7035)
		if (\extension_loaded('suhosin'))
		{
			if (($limit = ini_get('suhosin.memory_limit')) !== '')
			{
				@ini_set('memory_limit', $limit);
			}
		}
		else
		{
			@ini_set('memory_limit', -1);
		}

		$objDatabase = Database::getInstance();

		// Lock the files table
		$objDatabase->lockTables(array('tl_files'=>'WRITE'));

		// Reset the "found" flag
		$objDatabase->query("UPDATE tl_files SET found=''");

		$rootDir = System::getContainer()->getParameter('kernel.project_dir');

		/** @var \SplFileInfo[] $objFiles */
		$objFiles = new \RecursiveIteratorIterator(
			new SyncExclude(
				new \RecursiveDirectoryIterator(
					$rootDir . '/' . Config::get('uploadPath'),
					\FilesystemIterator::UNIX_PATHS|\FilesystemIterator::FOLLOW_SYMLINKS|\FilesystemIterator::SKIP_DOTS
				)
			), \RecursiveIteratorIterator::SELF_FIRST
		);

		$strLog = 'system/tmp/' . md5(uniqid(mt_rand(), true));

		// Open the log file
		$objLog = new File($strLog);
		$objLog->truncate();

		$arrModels = array();
		$arrFoldersToHash = array();
		$arrFoldersToCompare = array();

		// Create or update the database entries
		foreach ($objFiles as $objFile)
		{
			$strRelpath = StringUtil::stripRootDir($objFile->getPathname());

			// Get all subfiles in a single query
			if ($objFile->isDir())
			{
				$objSubfiles = FilesModel::findMultipleFilesByFolder($strRelpath);

				if ($objSubfiles !== null)
				{
					while ($objSubfiles->next())
					{
						$arrModels[$objSubfiles->path] = $objSubfiles->current();
					}
				}
			}

			// Get the model
			if (isset($arrModels[$strRelpath]))
			{
				/** @var Model $objModel */
				$objModel = $arrModels[$strRelpath];
			}
			else
			{
				$objModel = FilesModel::findByPath($strRelpath);
			}

			if ($objModel === null)
			{
				// Add a log entry
				$objLog->append("[Added] $strRelpath");

				// Get the parent folder
				$strParent = \dirname($strRelpath);

				// Get the parent ID
				if ($strParent == Config::get('uploadPath'))
				{
					$strPid = null;
				}
				else
				{
					$objParent = FilesModel::findByPath($strParent);

					if ($objParent === null)
					{
						throw new \Exception("No parent entry for $strParent");
					}

					$strPid = $objParent->uuid;
				}

				// Create the file or folder
				if (is_file($rootDir . '/' . $strRelpath))
				{
					$objFile = new File($strRelpath);

					$objModel = new FilesModel();
					$objModel->pid       = $strPid;
					$objModel->tstamp    = time();
					$objModel->name      = $objFile->name;
					$objModel->type      = 'file';
					$objModel->path      = $objFile->path;
					$objModel->extension = $objFile->extension;
					$objModel->found     = 2;
					$objModel->hash      = $objFile->hash;
					$objModel->uuid      = $objDatabase->getUuid();
					$objModel->save();
				}
				else
				{
					$objFolder = new Folder($strRelpath);

					$objModel = new FilesModel();
					$objModel->pid       = $strPid;
					$objModel->tstamp    = time();
					$objModel->name      = $objFolder->name;
					$objModel->type      = 'folder';
					$objModel->path      = $objFolder->path;
					$objModel->extension = '';
					$objModel->found     = 2;
					$objModel->uuid      = $objDatabase->getUuid();
					$objModel->save();

					$arrFoldersToHash[] = $strRelpath;
				}
			}
			else
			{
				if ($objFile->isDir())
				{
					$arrFoldersToCompare[] = $objModel;
				}
				else
				{
					// Check whether the MD5 hash has changed
					$strHash = (new File($strRelpath))->hash;
					$strType = ($objModel->hash != $strHash) ? 'Changed' : 'Unchanged';

					// Add a log entry
					$objLog->append("[$strType] $strRelpath");

					// Update the record
					$objModel->found = 1;
					$objModel->hash  = $strHash;
					$objModel->save();
				}
			}
		}

		// Update the folder hashes from bottom up after all file hashes are set
		foreach (array_reverse($arrFoldersToHash) as $strPath)
		{
			$objModel = FilesModel::findByPath($strPath);
			$objModel->hash = static::getFolderHash($strPath);
			$objModel->save();
		}

		// Compare the folders after all hashes are set
		foreach (array_reverse($arrFoldersToCompare) as $objModel)
		{
			// Check whether the MD5 hash has changed
			$strHash = static::getFolderHash($objModel->path);
			$strType = ($objModel->hash != $strHash) ? 'Changed' : 'Unchanged';

			// Add a log entry
			$objLog->append("[$strType] {$objModel->path}");

			// Update the record
			$objModel->found = 1;
			$objModel->hash  = $strHash;
			$objModel->save();
		}

		// Check for left-over entries in the DB
		$objFiles = FilesModel::findByFound('');

		if ($objFiles !== null)
		{
			$arrMapped = array();
			$arrPidUpdate = array();

			/** @var Collection|FilesModel $objFiles */
			while ($objFiles->next())
			{
				$objFound = FilesModel::findBy(array('hash=?', 'found=2'), $objFiles->hash);

				if ($objFound !== null)
				{
					// Check for matching file names if the result is ambiguous (see #5644)
					if ($objFound->count() > 1)
					{
						while ($objFound->next())
						{
							if ($objFound->name == $objFiles->name)
							{
								$objFound = $objFound->current();
								break;
							}
						}
					}

					// If another file has been mapped already, delete the entry (see #6008)
					if (\in_array($objFound->path, $arrMapped))
					{
						$objLog->append("[Deleted] {$objFiles->path}");
						$objFiles->delete();
						continue;
					}

					$arrMapped[] = $objFound->path;

					// Store the PID change
					if ($objFiles->type == 'folder')
					{
						$arrPidUpdate[$objFound->uuid] = $objFiles->uuid;
					}

					// Add a log entry BEFORE changing the object
					$objLog->append("[Moved] {$objFiles->path} to {$objFound->path}");

					// Update the original entry
					$objFiles->pid    = $objFound->pid;
					$objFiles->tstamp = $objFound->tstamp;
					$objFiles->name   = $objFound->name;
					$objFiles->type   = $objFound->type;
					$objFiles->path   = $objFound->path;
					$objFiles->found  = 1;

					// Delete the newer (duplicate) entry
					$objFound->delete();

					// Then save the modified original entry (prevents duplicate key errors)
					$objFiles->save();
				}
				else
				{
					// Add a log entry BEFORE changing the object
					$objLog->append("[Deleted] {$objFiles->path}");

					// Delete the entry if the resource has gone
					$objFiles->delete();
				}
			}

			// Update the PID of the child records
			if (!empty($arrPidUpdate))
			{
				foreach ($arrPidUpdate as $from=>$to)
				{
					$objChildren = FilesModel::findByPid($from);

					if ($objChildren !== null)
					{
						while ($objChildren->next())
						{
							$objChildren->pid = $to;
							$objChildren->save();
						}
					}
				}
			}
		}

		// Close the log file
		$objLog->close();

		// Reset the found flag
		$objDatabase->query("UPDATE tl_files SET found=1 WHERE found=2");

		// Unlock the tables
		$objDatabase->unlockTables();

		// Return the path to the log file
		return $strLog;
	}
Synchronize the file system with the database

@return string The path to the synchronization log file

@throws \Exception If a parent ID entry is missing