-
-
Save jokumer/09626089468a112a8ca4e8f33f099886 to your computer and use it in GitHub Desktop.
| <?php | |
| declare(strict_types=1); | |
| namespace Vendor\Extension\ExternalImport\Transformation; | |
| use Cobweb\ExternalImport\ImporterAwareInterface; | |
| use Cobweb\ExternalImport\ImporterAwareTrait; | |
| use TYPO3\CMS\Core\Context\Context; | |
| use TYPO3\CMS\Core\Database\Connection; | |
| use TYPO3\CMS\Core\Database\ConnectionPool; | |
| use TYPO3\CMS\Core\Database\Query\QueryBuilder; | |
| use TYPO3\CMS\Core\Resource\Folder; | |
| use TYPO3\CMS\Core\Resource\ResourceFactory; | |
| use TYPO3\CMS\Core\Utility\GeneralUtility; | |
| /** | |
| * Transformation class for External Import to store multiple assets during import | |
| * Use this class together with EXT:external_import | |
| * See https://github.com/cobwebch/external_import | |
| * | |
| * @package TYPO3 | |
| * @copyright Copyright and license information, please read the LICENSE.txt | |
| * @license http://www.gnu.org/licenses/gpl.html GNU General Public License, version 2 or later | |
| */ | |
| class ImageTransformation implements ImporterAwareInterface | |
| { | |
| use ImporterAwareTrait; | |
| /** | |
| * @var string Used to return a dummy identifier in preview mode | |
| */ | |
| static public $previewMessage = 'Preview mode. Image not handled, nor saved.'; | |
| /** | |
| * @var ResourceFactory | |
| */ | |
| protected $resourceFactory; | |
| /** | |
| * @var Folder[] Array of Folder objects | |
| */ | |
| protected $storageFolders = []; | |
| public function __construct() | |
| { | |
| $this->resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class); | |
| } | |
| /** | |
| * Gets multiple images from a string containing URI's and saves it into the given file storage and path. | |
| * This is adjusted for a special usage | |
| * - where images from URL's are no longer available | |
| * - multiple images are available for FAL field of a record | |
| * | |
| * The parameters array is expected to contain the following information: | |
| * | |
| * - "storage": a combined FAL identifier to a folder (e.g. "1:imported_images") | |
| * - "importSourcePath": Import source folder on host or any URL path like http://www.forumdfael.dk/regfoto/ | |
| * - "defaultExtension": a file extension in case it cannot be found in the URI (e.g. "jpg") | |
| * - "referenceUid": the reference field like 'referenceUid' in general configuration | |
| * - "updateTable": table name of record which gets created or updated | |
| * - "updateField": field name of record which gets created or updated | |
| * | |
| * @param array $record The full record that is being transformed | |
| * @param string $index The index of the field to transform | |
| * @param array $parameters Additional parameters from the TCA | |
| * @return mixed Uid of the saved sys_file record (or a message in preview mode) | |
| * @throws \TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException | |
| * @throws \TYPO3\CMS\Core\Resource\Exception\ExistingTargetFileNameException | |
| */ | |
| public function importAssetsFromCommaseparatedStringOfUrls(array $record, string $index, array $parameters) | |
| { | |
| // In preview mode, we don't want to handle or save images and just return a dummy string | |
| if ($this->importer->isPreview()) { | |
| return self::$previewMessage; | |
| } | |
| // Get urls from commaseparated string | |
| if (empty($record[$index])) { | |
| // If there's no value to handle, return null | |
| return null; | |
| } else { | |
| $urls = GeneralUtility::trimExplode(',', $record[$index]); | |
| } | |
| // Save images from urls and get file uids | |
| if (!empty($urls)) { | |
| $fileUids = []; | |
| foreach ($urls as $url) { | |
| $fileUid = $this->saveImageFromUri($url, $parameters, true); | |
| if ($fileUid) { | |
| $fileUids[$fileUid] = $fileUid; | |
| } | |
| } | |
| } | |
| // Set file references for current record | |
| if (!empty($fileUids)) { | |
| $insertUids = []; | |
| $currentDBRecord = $this->getRecordBy($parameters['updateTable'], $parameters['referenceUid'], $record[$parameters['referenceUid']]); | |
| if (isset($currentDBRecord['uid']) && (int)$currentDBRecord['uid']) { | |
| $i = 1; | |
| foreach ($fileUids as $fileUid) { | |
| if ((int)$fileUid) { | |
| $insertUid = $this->setFileReference($fileUid, $currentDBRecord['uid'], $currentDBRecord['pid'], $i, $parameters); | |
| if ($insertUid) { | |
| $insertUids[] = $insertUid; | |
| } | |
| $i++; | |
| } | |
| } | |
| } | |
| } | |
| // Set counter for current record assets | |
| if (!empty($insertUids)) { | |
| // Update foreign count reference | |
| $currentDBRecord = $this->getRecordBy($parameters['updateTable'], $parameters['referenceUid'], $record[$parameters['referenceUid']]); | |
| $countExist = (int)$currentDBRecord[$parameters['updateField']]; | |
| $countInserts = (int)count($insertUids); | |
| $countNew = $countExist + $countInserts; | |
| $this->updateForeignCountReference($countNew, $parameters['updateTable'], $parameters['updateField'], $currentDBRecord['uid']); | |
| } | |
| // Leave this transformation without any value (avoid update) | |
| return null; | |
| } | |
| /** | |
| * Gets multiple images from a HTML string containing URI's and saves it into the given file storage and path. | |
| * | |
| * The parameters array is expected to contain the following information: | |
| * | |
| * - "storage": a combined FAL identifier to a folder (e.g. "1:imported_images") | |
| * - "importSourcePath": Import source folder on host or any URL path like http://www.domain.tld/assets/ | |
| * - "defaultExtension": a file extension in case it cannot be found in the URI (e.g. "jpg") | |
| * - "referenceUid": the reference field like 'referenceUid' in general configuration | |
| * - "updateTable": table name of record which gets created or updated | |
| * - "updateField": field name of record which gets created or updated | |
| * - "downloadFileExtensions": comma separated list of file extension to download, if missing, all are downloaded | |
| * | |
| * @param array $record The full record that is being transformed | |
| * @param string $index The index of the field to transform | |
| * @param array $parameters Additional parameters from the TCA | |
| * @return mixed Uid of the saved sys_file record (or a message in preview mode) | |
| * @throws \TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException | |
| * @throws \TYPO3\CMS\Core\Resource\Exception\ExistingTargetFileNameException | |
| */ | |
| public function importAssetsFromHtml(array $record, string $index, array $parameters) | |
| { | |
| // If there's no value to handle, return null | |
| if (empty($record[$index])) { | |
| return null; | |
| } | |
| // In preview mode, we don't want to handle or save images and just return a dummy string | |
| if ($this->importer->isPreview()) { | |
| return self::$previewMessage; | |
| } | |
| // Parse html | |
| $html = $record[$index]; | |
| $doc = new \DOMDocument(); | |
| $doc->loadHTML($html); | |
| $xpath = new \DOMXPath($doc); | |
| // Get HTML <img src="?" /> | |
| $images = []; | |
| $imageDOMNodeList = $xpath->query('//img/@src'); | |
| if (!empty($imageDOMNodeList)) { | |
| foreach($imageDOMNodeList as $imageDOMNode) { | |
| if ($imageDOMNode->value) { | |
| $images[] = $imageDOMNode->value; | |
| } | |
| } | |
| } | |
| // Get HTML <a href="?" /> | |
| $downloads = []; | |
| $downloadDOMNodeList = $xpath->query('//a/@href'); | |
| if (!empty($downloadDOMNodeList)) { | |
| foreach($downloadDOMNodeList as $downloadDOMNode) { | |
| if ($downloadDOMNode->value) { | |
| $download = $downloadDOMNode->value; | |
| $downloadUrlParts = parse_url($download); | |
| if (isset($downloadUrlParts['path'])) { | |
| $downloadPathParts = pathinfo($downloadUrlParts['path']); | |
| if (!empty($downloadPathParts['extension'])) { | |
| if (isset($parameters['downloadFileExtensions'])) { | |
| $downloadFileExtensions = GeneralUtility::trimExplode(',', $parameters['downloadFileExtensions']); | |
| if (in_array($downloadPathParts['extension'],$downloadFileExtensions)) { | |
| $downloads[] = $downloadDOMNode->value; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // Collect all sources | |
| $sources = []; | |
| if (!empty($images)) { | |
| $sources = array_merge_recursive($sources, $images); | |
| } | |
| if (!empty($downloads)) { | |
| $sources = array_merge_recursive($sources, $downloads); | |
| } | |
| // Handle sources, insert sys_file_references | |
| if (!empty($sources)) { | |
| $insertedReferences = []; | |
| $sources = array_unique($sources); | |
| $i = 1; | |
| foreach ($sources as $src) { | |
| $urlParts = parse_url($src); | |
| if (isset($urlParts['scheme']) && isset($urlParts['host'])) { | |
| // for path like src="https://domain.tld/assets/.." | |
| if ($urlParts['host'] == 'www.domain.tld') { | |
| $respectImportSourcePath = true; | |
| } else { | |
| $respectImportSourcePath = false; | |
| } | |
| $url = $src; | |
| } else { | |
| // @todo - path like src="/assets/.." | |
| $url = null; | |
| } | |
| // Save images from urls and get file uids | |
| if (!empty($url)) { | |
| $fileUid = $this->saveImageFromUri($url, $parameters, $respectImportSourcePath); | |
| } | |
| // Set file references for current record | |
| if (!empty($fileUid)) { | |
| $currentDBRecord = $this->getRecordBy($parameters['updateTable'], $parameters['referenceUid'], | |
| $record[$parameters['referenceUid']]); | |
| $insertUid = $this->setFileReference($fileUid, $currentDBRecord['uid'], $currentDBRecord['pid'], | |
| $i, $parameters); | |
| if ($insertUid) { | |
| $insertedReferences[] = $insertUid; | |
| } | |
| $i++; | |
| } | |
| } | |
| } | |
| // Set counter for current record assets | |
| if (!empty($insertedReferences)) { | |
| // Update foreign count reference | |
| $currentDBRecord = $this->getRecordBy($parameters['updateTable'], $parameters['referenceUid'], $record[$parameters['referenceUid']]); | |
| $countExist = (int)$currentDBRecord[$parameters['updateField']]; | |
| $countInserts = (int)count($insertedReferences); | |
| $countNew = $countExist + $countInserts; | |
| $this->updateForeignCountReference($countNew, $parameters['updateTable'], $parameters['updateField'], $currentDBRecord['uid']); | |
| } | |
| // Leave this transformation without any value (avoid update) | |
| return null; | |
| } | |
| /** | |
| * Gets an image from a URI and saves it into the given file storage and path. | |
| * This is adjusted for a special usage, where images from URL's are no longer available | |
| * | |
| * The parameters array is expected to contain the following information: | |
| * | |
| * - "storage": a combined FAL identifier to a folder (e.g. "1:imported_images") | |
| * - "defaultExtension": a file extension in case it cannot be found in the URI (e.g. "jpg") | |
| * | |
| * @param string $url The full record that is being transformed | |
| * @param array $parameters Additional parameters from the TCA | |
| * @param array $respectImportSourcePath Force using importSourcePath configuration for any urls | |
| * @return mixed Uid of the saved sys_file record (or a message in preview mode) | |
| * @throws \TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException | |
| * @throws \TYPO3\CMS\Core\Resource\Exception\ExistingTargetFileNameException | |
| */ | |
| protected function saveImageFromUri($url = '', array $parameters, $respectImportSourcePath = true) | |
| { | |
| // If there's no value to handle, return null | |
| if (empty($url)) { | |
| return null; | |
| } | |
| // In preview mode, we don't want to handle or save images and just return a dummy string | |
| if ($this->importer->isPreview()) { | |
| return self::$previewMessage; | |
| } | |
| // Throw an exception if storage is not defined | |
| if (empty($parameters['storage'])) { | |
| throw new \InvalidArgumentException( | |
| 'No storage given for importing files', | |
| 1602170223 | |
| ); | |
| } | |
| // Ensure the storage folder is loaded (we keep a local cache of folder objects for efficiency) | |
| if ($this->storageFolders[$parameters['storage']] === null) { | |
| $this->storageFolders[$parameters['storage']] = $this->resourceFactory->getFolderObjectFromCombinedIdentifier( | |
| $parameters['storage'] | |
| ); | |
| } | |
| // Assemble a file name | |
| $urlParts = parse_url($url); | |
| $pathParts = pathinfo($urlParts['path']); | |
| $fileName = $pathParts['basename']; | |
| if (empty($pathParts['extension'])) { | |
| if (isset($parameters['defaultExtension'])) { | |
| $fileName .= '.' . $parameters['defaultExtension']; | |
| } else { | |
| throw new \InvalidArgumentException( | |
| sprintf( | |
| 'No extension could be found for imported file %s', | |
| $url | |
| ), | |
| 1602170422 | |
| ); | |
| } | |
| } | |
| $fileName = $this->storageFolders[$parameters['storage']]->getStorage()->sanitizeFileName( | |
| $fileName, | |
| $this->storageFolders[$parameters['storage']] | |
| ); | |
| // Check if the file already exists | |
| if ($this->storageFolders[$parameters['storage']]->hasFile($fileName)) { | |
| $fileObject = $this->resourceFactory->getFileObjectFromCombinedIdentifier( | |
| $parameters['storage'] . '/' . $fileName | |
| ); | |
| // If the file does not yet exist locally, grab it from the remote server and add it to predefined storage | |
| } else { | |
| $temporaryFile = GeneralUtility::tempnam('external_import_uploads'); | |
| // Adjust import source if path given | |
| if (isset($parameters['importSourcePath']) && $respectImportSourcePath) { | |
| $fileUrl = $this->getBaseUri() . ltrim(rtrim($parameters['importSourcePath'], '/'), '/') . '/' . $fileName; | |
| } else { | |
| $fileUrl = $url; | |
| } | |
| $file = GeneralUtility::getUrl($fileUrl, 0, null, $report); | |
| // If the file could not be fetched, report and throw an exception | |
| if ($file === false) { | |
| // Avoid exeception, to import further | |
| return null; | |
| #$error = sprintf( | |
| # 'File %s could not be fetched.', | |
| # $url | |
| #); | |
| #if (isset($report['message'])) { | |
| # $error .= ' ' . sprintf( | |
| # 'Reason: %s (code: %s)', | |
| # $report['message'], | |
| # $report['error'] ?? 0 | |
| # ); | |
| #} | |
| #throw new \TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException( | |
| # $error, | |
| # 1613555057 | |
| #); | |
| } | |
| GeneralUtility::writeFileToTypo3tempDir( | |
| $temporaryFile, | |
| $file | |
| ); | |
| $fileObject = $this->storageFolders[$parameters['storage']]->addFile( | |
| $temporaryFile, | |
| $fileName | |
| ); | |
| } | |
| // Return the file's ID | |
| return $fileObject->getUid(); | |
| } | |
| /** | |
| * Get record by field and value | |
| * | |
| * @return array|null | |
| */ | |
| protected function getRecordBy($tableName, $fieldName, $fieldvalue) | |
| { | |
| /** @var QueryBuilder $queryBuilder */ | |
| $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) | |
| ->getQueryBuilderForTable($tableName); | |
| // Remove all restrictions (ignore hidden, which is already set during imports) | |
| $queryBuilder | |
| ->getRestrictions() | |
| ->removeAll(); | |
| return $queryBuilder | |
| ->select('*') | |
| ->from($tableName) | |
| ->where( | |
| $queryBuilder->expr()->eq($fieldName, $fieldvalue) | |
| ) | |
| ->execute() | |
| ->fetch(); | |
| } | |
| /** | |
| * Get baseUri | |
| * | |
| * @return string | |
| */ | |
| protected function getBaseUri() | |
| { | |
| $baseUri = GeneralUtility::getIndpEnv('TYPO3_SITE_URL'); | |
| if (substr($baseUri, -1) !== '/') { | |
| $baseUri .= '/'; | |
| } | |
| return $baseUri; | |
| } | |
| /** | |
| * Set file reference for current record file | |
| * | |
| * @param int $uid_local | |
| * @param int $uid_foreign | |
| * @param int $pid | |
| * @param int $sorting | |
| * @param array $parameters Additional parameters from the TCA | |
| * @return int|null | |
| */ | |
| protected function setFileReference($uid_local, $uid_foreign, $pid, $sorting, $parameters) | |
| { | |
| $context = GeneralUtility::makeInstance(Context::class); | |
| $tableName = 'sys_file_reference'; | |
| $insertFields = [ | |
| 'pid' => (int)$pid, | |
| 'crdate' => $GLOBALS['EXEC_TIME'], | |
| 'tstamp' => $GLOBALS['EXEC_TIME'], | |
| 'cruser_id' => $context->getAspect('backend.user')->get('id'), | |
| 'uid_local' => (int)$uid_local, | |
| 'uid_foreign' => (int)$uid_foreign, | |
| 'tablenames' => $parameters['updateTable'], | |
| 'fieldname' => $parameters['updateField'], | |
| 'sorting_foreign' => (int)$sorting, | |
| 'table_local' => 'sys_file' | |
| ]; | |
| /** @var Connection $connection */ | |
| $connection = GeneralUtility::makeInstance(ConnectionPool::class) | |
| ->getConnectionForTable($tableName); | |
| try { | |
| $connection->insert($tableName, $insertFields); | |
| $sql_insert_id = $connection->lastInsertId($tableName); | |
| return $sql_insert_id; | |
| } catch (\Doctrine\DBAL\DBALException $e) { | |
| #$insertErrorMessage = $e->getMessage(); | |
| } | |
| } | |
| /** | |
| * Update count of foreign field reference for current record | |
| * | |
| * @param int $count | |
| * @param string $tableName | |
| * @param string $fieldName | |
| * @param int $recordUid | |
| * @return void | |
| */ | |
| protected function updateForeignCountReference($count, $tableName, $fieldName, $recordUid) | |
| { | |
| $updateFields = [ | |
| $fieldName => (int)$count | |
| ]; | |
| /** @var Connection $connection */ | |
| $connection = GeneralUtility::makeInstance(ConnectionPool::class) | |
| ->getConnectionForTable($tableName); | |
| try { | |
| $connection->update($tableName, $updateFields, ['uid' => (int)$recordUid]); | |
| } catch (\Doctrine\DBAL\DBALException $e) { | |
| #$updateErrorMessage = $e->getMessage(); | |
| } | |
| } | |
| } |
Do you have copied this class over to your own extension
ext/your_extension/Classes/ExternalImport/Transformation/ImageTransformation.php
and renamed Vendor\Extension to your requirements, both in PHP class and TCA?
You can adjust Vendor/Extension and the Path as you like, but Vendor/Extension is important to match with your extension.
Hi, yes, I have access to your class.
In preview mode your script seems to work. The step StoreDataStep shows the splitted entries in sys_file_reference.
But after running the synchronisation, nothing happens ...
Then I would start to debug inbetween code to find out, what is going wrong and when.
I try to find out why ...
Hi,
createNamedParameter() in function getRecordBy will do the Job for me. My referenceUid ist a string.
/**
* Get record by field and value
*
* @return array|null
*/
protected function getRecordBy($tableName, $fieldName, $fieldvalue)
{
/** @var QueryBuilder $queryBuilder */
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable($tableName);
// Remove all restrictions (ignore hidden, which is already set during imports)
$queryBuilder
->getRestrictions()
->removeAll();
return $queryBuilder
->select('*')
->from($tableName)
->where(
$queryBuilder->expr()->eq($fieldName, $queryBuilder->createNamedParameter($fieldvalue))
)
->execute()
->fetch();
}
createNamedParameter () is always a good choice;)
And a chrildren configuration is not needed
btw: thanks for your script
Thx @stephangrass for your improvements. I just published like you, to support cobwebch/external_import

Hi,
I can't get your Class to work. Here my TCA:
Where is the mistake?