Created
May 6, 2021 09:09
-
-
Save jokumer/09626089468a112a8ca4e8f33f099886 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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(); | |
} | |
} | |
} |
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Then I would start to debug inbetween code to find out, what is going wrong and when.