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=''");
/** @var \SplFileInfo[] $objFiles */
$objFiles = new \RecursiveIteratorIterator(new \Filter\SyncExclude(new \RecursiveDirectoryIterator(TL_ROOT . '/' . \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();
// Create or update the database entries
foreach ($objFiles as $objFile) {
$strRelpath = str_replace(TL_ROOT . '/', '', $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])) {
$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(TL_ROOT . '/' . $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->hash = $objFolder->hash;
$objModel->uuid = $objDatabase->getUuid();
$objModel->save();
}
} else {
// Check whether the MD5 hash has changed
$objResource = $objFile->isDir() ? new \Folder($strRelpath) : new \File($strRelpath);
$strType = $objModel->hash != $objResource->hash ? 'Changed' : 'Unchanged';
// Add a log entry
$objLog->append("[{$strType}] {$strRelpath}");
// Update the record
$objModel->found = 1;
$objModel->hash = $objResource->hash;
$objModel->save();
}
}
// Check for left-over entries in the DB
$objFiles = \FilesModel::findByFound('');
if ($objFiles !== null) {
$arrMapped = array();
$arrPidUpdate = array();
/** @var Model\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;
}