/** * Loads index-data into processedFileObject * * @param \TYPO3\CMS\Core\Resource\ProcessedFile $processedFileObject * @return boolean */ public function populateDataOfProcessedFileObject(\TYPO3\CMS\Core\Resource\ProcessedFile $processedFileObject) { /** @var $GLOBALS['TYPO3_DB'] \TYPO3\CMS\Core\Database\DatabaseConnection */ $recordData = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow('*', $this->table, 'original=' . intval($processedFileObject->getOriginalFile()->getUid()) . ' AND checksum=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($processedFileObject->calculateChecksum(), $this->table) . ' AND deleted=0'); // Update the properties if the data was found if (is_array($recordData)) { $processedFileObject->updateProperties($recordData); return TRUE; } else { return FALSE; } }
/** * Calculates the compatibility values * This is duplicate code taken from ContentObjectRenderer::getImgResource() * Ideally we should get rid of this code in both places. * * @param ProcessedFile $processedImage * @return array */ protected function getCompatibilityImageResourceValues(ProcessedFile $processedImage) { $hash = $processedImage->calculateChecksum(); if (isset($GLOBALS['TSFE']->tmpl->fileCache[$hash])) { $compatibilityImageResourceValues = $GLOBALS['TSFE']->tmpl->fileCache[$hash]; } else { $compatibilityImageResourceValues = array(0 => $processedImage->getProperty('width'), 1 => $processedImage->getProperty('height'), 2 => $processedImage->getExtension(), 3 => $processedImage->getPublicUrl(), 'origFile' => $processedImage->getOriginalFile()->getPublicUrl(), 'origFile_mtime' => $processedImage->getOriginalFile()->getModificationTime(), 'originalFile' => $processedImage->getOriginalFile(), 'processedFile' => $processedImage, 'fileCacheHash' => $hash); } return $compatibilityImageResourceValues; }
/** * Performs the update * * @param array &$databaseQueries Queries done in this update * @param mixed &$customMessages Custom messages * @return bool */ public function performUpdate(array &$databaseQueries, &$customMessages) { $db = $this->getDatabaseConnection(); // remove all invalid records which hold NULL values $db->exec_DELETEquery('sys_file_processedfile', 'width IS NULL or height IS NULL'); $factory = GeneralUtility::makeInstance(ResourceFactory::class); $join = 'sys_file_processedfile LEFT JOIN sys_registry ON entry_key = CAST(sys_file_processedfile.uid AS CHAR) AND entry_namespace = \'ProcessedFileChecksumUpdate\''; $res = $db->exec_SELECTquery('sys_file_processedfile.*', $join, 'entry_key IS NULL AND sys_file_processedfile.identifier <> \'\''); while ($processedFileRow = $db->sql_fetch_assoc($res)) { try { $storage = $factory->getStorageObject($processedFileRow['storage']); } catch (\InvalidArgumentException $e) { $storage = null; } if (!$storage) { // invalid storage, delete record, we can't take care of the associated file $db->exec_DELETEquery('sys_file_processedfile', 'uid=' . $processedFileRow['uid']); continue; } if ($storage->getDriverType() !== 'Local') { // non-local storage, we can't treat this, skip the record and mark it done $db->exec_INSERTquery('sys_registry', array('entry_namespace' => 'ProcessedFileChecksumUpdate', 'entry_key' => $processedFileRow['uid'])); continue; } $configuration = $storage->getConfiguration(); if ($configuration['pathType'] === 'relative') { $absoluteBasePath = PATH_site . $configuration['basePath']; } else { $absoluteBasePath = $configuration['basePath']; } $filePath = rtrim($absoluteBasePath, '/') . '/' . ltrim($processedFileRow['identifier'], '/'); try { $originalFile = $factory->getFileObject($processedFileRow['original']); } catch (\Exception $e) { // no original file there anymore, delete local file @unlink($filePath); $db->exec_DELETEquery('sys_file_processedfile', 'uid=' . $processedFileRow['uid']); continue; } $processedFileObject = new ProcessedFile($originalFile, '', array(), $processedFileRow); // calculate new checksum and name $newChecksum = $processedFileObject->calculateChecksum(); // if the checksum already matches, there is nothing to do if ($newChecksum !== $processedFileRow['checksum']) { $newName = str_replace($processedFileRow['checksum'], $newChecksum, $processedFileRow['name']); $newIdentifier = str_replace($processedFileRow['checksum'], $newChecksum, $processedFileRow['identifier']); $newFilePath = str_replace($processedFileRow['checksum'], $newChecksum, $filePath); // rename file if (@rename($filePath, $newFilePath)) { // save result back into database $fields = array('tstamp' => time(), 'identifier' => $newIdentifier, 'name' => $newName, 'checksum' => $newChecksum); $db->exec_UPDATEquery('sys_file_processedfile', 'uid=' . $processedFileRow['uid'], $fields); } // if the rename of the file failed, keep the record, but do not bother with it again } // remember we finished this record $db->exec_INSERTquery('sys_registry', array('entry_namespace' => 'ProcessedFileChecksumUpdate', 'entry_key' => $processedFileRow['uid'])); } $db->exec_DELETEquery('sys_registry', 'entry_namespace = \'ProcessedFileChecksumUpdate\''); $this->markWizardAsDone(); return true; }
/** * This method actually does the processing of files locally * * takes the original file (on remote storages this will be fetched from the remote server) * does the IM magic on the local server by creating a temporary typo3temp/ file * copies the typo3temp/ file to the processingfolder of the target storage * removes the typo3temp/ file * * @param \TYPO3\CMS\Core\Resource\ProcessedFile $processedFile * @param \TYPO3\CMS\Core\Resource\FileInterface $file * @param array $configuration * @return void */ protected function processImageCropResizeMask(\TYPO3\CMS\Core\Resource\ProcessedFile $processedFile, \TYPO3\CMS\Core\Resource\FileInterface $file, array $configuration) { // checks to see if m (the mask array) is defined $doMasking = is_array($configuration['maskImages']) && $GLOBALS['TYPO3_CONF_VARS']['GFX']['im']; // @todo: is it ok that we use tslib (=FE) here? /** @var $gifBuilder tslib_gifbuilder */ $gifBuilder = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('tslib_gifbuilder'); $gifBuilder->init(); // @todo: this is not clean yet if (!trim($configuration['fileExtension'])) { $configuration['fileExtension'] = 'web'; $targetFileExtension = $file->getExtension(); } elseif ($doMasking) { $targetFileExtension = $file->getExtension() == $gifBuilder->gifExtension ? $gifBuilder->gifExtension : 'jpg'; } else { $targetFileExtension = $configuration['fileExtension']; } $originalFileName = $file->getForLocalProcessing(FALSE); $targetFolder = $this->storage->getProcessingFolder(); $targetFileName = 'previewcrm_' . $processedFile->calculateChecksum() . '.' . $targetFileExtension; // @todo: implement meaningful TempFileIndex if ($configuration['useSample']) { $gifBuilder->scalecmd = '-sample'; } $options = array(); if ($configuration['maxWidth']) { $options['maxW'] = $configuration['maxWidth']; } if ($configuration['maxHeight']) { $options['maxH'] = $configuration['maxHeight']; } if ($configuration['minWidth']) { $options['minW'] = $configuration['minWidth']; } if ($configuration['minHeight']) { $options['minH'] = $configuration['minHeight']; } $options['noScale'] = $configuration['noScale']; $configuration['additionalParameters'] = $this->modifyImageMagickStripProfileParameters($configuration['additionalParameters'], $configuration); // Do the actual processing if (!$targetFolder->hasFile($targetFileName)) { if (!$doMasking) { // Normal situation (no masking) // the result info is an array with 0=width,1=height,2=extension,3=filename list($targetWidth, $targetHeight, $targetExtension, $temporaryFileName) = $gifBuilder->imageMagickConvert($originalFileName, $configuration['fileExtension'], $configuration['width'], $configuration['height'], $configuration['additionalParameters'], $configuration['frame'], $options); } else { $temporaryFileName = $gifBuilder->tempPath . $targetFileName; $maskImage = $configuration['maskImages']['maskImage']; $maskBackgroundImage = $configuration['maskImages']['backgroundImage']; if ($maskImage instanceof \TYPO3\CMS\Core\Resource\FileInterface && $maskBackgroundImage instanceof \TYPO3\CMS\Core\Resource\FileInterface) { $negate = $GLOBALS['TYPO3_CONF_VARS']['GFX']['im_negate_mask'] ? ' -negate' : ''; $temporaryExtension = 'png'; if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['im_mask_temp_ext_gif']) { // If ImageMagick version 5+ $temporaryExtension = $gifBuilder->gifExtension; } $tempFileInfo = $gifBuilder->imageMagickConvert($originalFileName, $temporaryExtension, $configuration['width'], $configuration['height'], $configuration['additionalParameters'], $configuration['frame'], $options); if (is_array($tempFileInfo)) { $maskBottomImage = $configuration['maskImages']['maskBottomImage']; if ($maskBottomImage instanceof $maskBottomImage) { $maskBottomImageMask = $configuration['maskImages']['maskBottomImageMask']; } // Scaling: **** $tempScale = array(); $command = '-geometry ' . $tempFileInfo[0] . 'x' . $tempFileInfo[1] . '!'; $command = $this->modifyImageMagickStripProfileParameters($command, $configuration); $tmpStr = $gifBuilder->randomName(); // m_mask $tempScale['m_mask'] = $tmpStr . '_mask.' . $temporaryExtension; $gifBuilder->imageMagickExec($maskImage->getForLocalProcessing(TRUE), $tempScale['m_mask'], $command . $negate); // m_bgImg $tempScale['m_bgImg'] = $tmpStr . '_bgImg.' . trim($GLOBALS['TYPO3_CONF_VARS']['GFX']['im_mask_temp_ext_noloss']); $gifBuilder->imageMagickExec($maskBackgroundImage->getForLocalProcessing(), $tempScale['m_bgImg'], $command); // m_bottomImg / m_bottomImg_mask if ($maskBottomImage instanceof \TYPO3\CMS\Core\Resource\FileInterface && $maskBottomImageMask instanceof \TYPO3\CMS\Core\Resource\FileInterface) { $tempScale['m_bottomImg'] = $tmpStr . '_bottomImg.' . $temporaryExtension; $gifBuilder->imageMagickExec($maskBottomImage->getForLocalProcessing(), $tempScale['m_bottomImg'], $command); $tempScale['m_bottomImg_mask'] = $tmpStr . '_bottomImg_mask.' . $temporaryExtension; $gifBuilder->imageMagickExec($maskBottomImageMask->getForLocalProcessing(), $tempScale['m_bottomImg_mask'], $command . $negate); // BEGIN combining: // The image onto the background $gifBuilder->combineExec($tempScale['m_bgImg'], $tempScale['m_bottomImg'], $tempScale['m_bottomImg_mask'], $tempScale['m_bgImg']); } // The image onto the background $gifBuilder->combineExec($tempScale['m_bgImg'], $tempFileInfo[3], $tempScale['m_mask'], $temporaryFileName); // Unlink the temp-images... foreach ($tempScale as $file) { if (@is_file($file)) { unlink($file); } } } } // Finish off list($targetWidth, $targetHeight) = $gifBuilder->getImageDimensions($temporaryFileName); } // Temporary image was created if (file_exists($temporaryFileName)) { $updatedProperties = array('width' => $targetWidth, 'height' => $targetHeight); // ImageMagick did not have to do anything, as it is already there... if ($originalFileName !== $temporaryFileName) { \TYPO3\CMS\Core\Utility\GeneralUtility::fixPermissions($temporaryFileName); // Copy the temporary file to the processedFolder // this is done here, as the driver can do this without worrying // about existing ProcessedFile objects // or permissions in the storage // for "remote" storages this means "uploading" the file to the storage again // for the virtual storage, it is merely a thing of "copying a file from typo3temp/ to typo3temp/_processed_" $this->driver->addFile($temporaryFileName, $targetFolder, $targetFileName, $processedFile); // Remove the temporary file as it's not needed anymore \TYPO3\CMS\Core\Utility\GeneralUtility::unlink_tempfile($temporaryFileName); } else { } $processedFile->updateProperties($updatedProperties); $processedFile->setProcessed(TRUE); } } }
/** * Performs the update * * @param array &$databaseQueries Queries done in this update * @param mixed &$customMessages Custom messages * @return bool */ public function performUpdate(array &$databaseQueries, &$customMessages) { $registry = GeneralUtility::makeInstance(Registry::class); $factory = GeneralUtility::makeInstance(ResourceFactory::class); $fileConnection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('sys_file_processedfile'); $firstUid = $registry->get('core', 'ProcessedFileChecksumUpdate'); // Remove all invalid records which hold NULL values $queryBuilder = $fileConnection->createQueryBuilder(); $queryBuilder->delete('sys_file_processedfile')->orWhere($queryBuilder->expr()->isNull('width'), $queryBuilder->expr()->isNull('height'))->execute(); // Get all other rows $queryBuilder = $fileConnection->createQueryBuilder(); $queryBuilder = $queryBuilder->select('*')->from('sys_file_processedfile')->orderBy('uid'); // If there was a start trigger, use it if ($firstUid !== null && (int) $firstUid > 0) { $queryBuilder->where($queryBuilder->expr()->gt('uid', $queryBuilder->createNamedParameter($firstUid, \PDO::PARAM_INT))); } $statement = $queryBuilder->execute(); while ($processedFileRow = $statement->fetch()) { try { $storage = $factory->getStorageObject($processedFileRow['storage']); } catch (\InvalidArgumentException $e) { $storage = null; } if (!$storage) { // Invalid storage, delete record, we can't take care of the associated file $fileConnection->delete('sys_file_processedfile', ['uid' => (int) $processedFileRow['uid']]); $registry->set('core', 'ProcessedFileChecksumUpdate', (int) $processedFileRow['uid']); continue; } if ($storage->getDriverType() !== 'Local') { // Non-local storage, we can't treat this, skip the record and mark it done $registry->set('core', 'ProcessedFileChecksumUpdate', (int) $processedFileRow['uid']); continue; } $configuration = $storage->getConfiguration(); if ($configuration['pathType'] === 'relative') { $absoluteBasePath = PATH_site . $configuration['basePath']; } else { $absoluteBasePath = $configuration['basePath']; } $filePath = rtrim($absoluteBasePath, '/') . '/' . ltrim($processedFileRow['identifier'], '/'); try { $originalFile = $factory->getFileObject($processedFileRow['original']); } catch (\Exception $e) { // No original file there anymore, delete local file @unlink($filePath); $fileConnection->delete('sys_file_processedfile', ['uid' => (int) $processedFileRow['uid']]); $registry->set('core', 'ProcessedFileChecksumUpdate', (int) $processedFileRow['uid']); continue; } $processedFileObject = new ProcessedFile($originalFile, '', [], $processedFileRow); // calculate new checksum and name $newChecksum = $processedFileObject->calculateChecksum(); // if the checksum already matches, there is nothing to do if ($newChecksum !== $processedFileRow['checksum']) { $newName = str_replace($processedFileRow['checksum'], $newChecksum, $processedFileRow['name']); $newIdentifier = str_replace($processedFileRow['checksum'], $newChecksum, $processedFileRow['identifier']); $newFilePath = str_replace($processedFileRow['checksum'], $newChecksum, $filePath); // rename file if (@rename($filePath, $newFilePath)) { // save result back into database $fields = ['tstamp' => time(), 'identifier' => $newIdentifier, 'name' => $newName, 'checksum' => $newChecksum]; $fileConnection->update('sys_file_processedfile', $fields, ['uid' => (int) $processedFileRow['uid']]); } // if the rename of the file failed, keep the record, but do not bother with it again } $registry->set('core', 'ProcessedFileChecksumUpdate', (int) $processedFileRow['uid']); } $registry->remove('core', 'ProcessedFileChecksumUpdate'); $this->markWizardAsDone(); return true; }
/** * Calculates the compatibility values * This is duplicate code taken from ContentObjectRenderer::getImgResource() * Ideally we should get rid of this code in both places. * * @param ProcessedFile $processedImage * @return array */ protected function getCompatibilityImageResourceValues(ProcessedFile $processedImage) { $hash = $processedImage->calculateChecksum(); if (isset($GLOBALS['TSFE']->tmpl->fileCache[$hash])) { $compatibilityImageResourceValues = $GLOBALS['TSFE']->tmpl->fileCache[$hash]; } else { $compatibilityImageResourceValues = array( 0 => $processedImage->getProperty('width'), 1 => $processedImage->getProperty('height'), 2 => $processedImage->getExtension(), 3 => $processedImage->getPublicUrl(), 'origFile' => $processedImage->getOriginalFile()->getPublicUrl(), 'origFile_mtime' => $processedImage->getOriginalFile()->getModificationTime(), // This is needed by \TYPO3\CMS\Frontend\Imaging\GifBuilder, // in order for the setup-array to create a unique filename hash. 'originalFile' => $processedImage->getOriginalFile(), 'processedFile' => $processedImage, 'fileCacheHash' => $hash ); } return $compatibilityImageResourceValues; }