From cf94d8da829652a3b168f71af43d185b71f612b8 Mon Sep 17 00:00:00 2001 From: Rico Sonntag Date: Fri, 3 Feb 2023 11:43:37 +0000 Subject: [PATCH] GH-214: WIP --- .../Controller/ImageRenderingController.php | 3 +- Classes/Controller/SelectImageController.php | 3 +- Classes/DataHandling/DataHandler.php | 143 +++++ Classes/Database/RteImagesDbHook.php | 517 ++++++++++-------- .../TransformRteDataProvider.php | 80 +++ Configuration/Backend/Routes.php | 2 + Configuration/RTE/Plugin.yaml | 6 +- .../Public/JavaScript/Plugins/typo3image.js | 100 ++-- ext_localconf.php | 15 +- 9 files changed, 572 insertions(+), 297 deletions(-) create mode 100644 Classes/DataHandling/DataHandler.php create mode 100644 Classes/FormDataProvider/TransformRteDataProvider.php diff --git a/Classes/Controller/ImageRenderingController.php b/Classes/Controller/ImageRenderingController.php index 7f932ea..9a3fc46 100644 --- a/Classes/Controller/ImageRenderingController.php +++ b/Classes/Controller/ImageRenderingController.php @@ -19,6 +19,7 @@ use TYPO3\CMS\Core\Resource\ResourceFactory; use TYPO3\CMS\Core\Resource\Service\MagicImageService; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Utility\DebuggerUtility; use TYPO3\CMS\Frontend\Plugin\AbstractPlugin; use function get_class; @@ -230,7 +231,7 @@ protected function getMagicImageService(): MagicImageService protected function isExternalImage(string $imageSource): bool { // https://github.com/netresearch/t3x-rte_ckeditor_image/issues/187 - if (strpos($imageSource, '/typo3/image/process?token') !== false) { + if (strpos($imageSource, 'typo3/image/process?token') !== false) { // is a 11LTS backend processing url only valid for BE users, thus reprocessing needed return false; } diff --git a/Classes/Controller/SelectImageController.php b/Classes/Controller/SelectImageController.php index 1ef3abf..28654c8 100644 --- a/Classes/Controller/SelectImageController.php +++ b/Classes/Controller/SelectImageController.php @@ -107,8 +107,7 @@ public function infoAction(ServerRequestInterface $request): ResponseInterface $file = $this->getImage((int) $id); $processedFile = $this->processImage($file, $params); - - $lang = $this->getLanguageService(); + $lang = $this->getLanguageService(); // Include language files $this->getLanguageService() diff --git a/Classes/DataHandling/DataHandler.php b/Classes/DataHandling/DataHandler.php new file mode 100644 index 0000000..d96b048 --- /dev/null +++ b/Classes/DataHandling/DataHandler.php @@ -0,0 +1,143 @@ + + * @license https://www.gnu.org/licenses/agpl-3.0.de.html + * @link https://www.netresearch.de + */ +class DataHandler +{ + /** + * Process the modified text from TCA text field before its stored in the database. + * + * @param string $status + * @param string $table + * @param string $id + * @param array &$fieldArray + * @param \TYPO3\CMS\Core\DataHandling\DataHandler $dataHandler + * + * @return void + */ + public function processDatamap_postProcessFieldArray( + string $status, + string $table, + string $id, + array &$fieldArray, + \TYPO3\CMS\Core\DataHandling\DataHandler $dataHandler + ): void { + +//DebuggerUtility::var_dump(__METHOD__); +//DebuggerUtility::var_dump($status); +//DebuggerUtility::var_dump($table); +//DebuggerUtility::var_dump($id); +//DebuggerUtility::var_dump($fieldArray); + + foreach ($fieldArray as $field => $fieldValue) { + // The field must be editable. Checking if a value for language can be changed. + if (($GLOBALS['TCA'][$table]['ctrl']['languageField'] ?? false) + && ((string)$GLOBALS['TCA'][$table]['ctrl']['languageField']) === ((string)$field) + && !$this->getBackendUser()->checkLanguageAccess($fieldValue) + ) { + continue; + } + + // Ignore disabled fields + if ($dataHandler->data_disableFields[$table][$id][$field] ?? false) { + continue; + } + + // Ignore not existing fields in TCA definition + if (!isset($GLOBALS['TCA'][$table]['columns'][$field])) { + continue; + } + + // Getting config for the field + $tcaFieldConf = $this->resolveFieldConfigurationAndRespectColumnsOverrides( + $dataHandler, + $table, + $field + ); + + // Handle only fields of type "text" + if (empty($tcaFieldConf['type']) + || ($tcaFieldConf['type'] !== 'text') + ) { + continue; + } + + // Ignore all none RTE text fields + if (!isset($tcaFieldConf['enableRichtext']) + || ($tcaFieldConf['enableRichtext'] === false) + ) { + continue; + } + + $rteImageDbHook = GeneralUtility::makeInstance(RteImagesDbHook::class); + $rteHtmlParser = GeneralUtility::makeInstance(RteHtmlParser::class); + + $fieldArray[$field] = $rteImageDbHook->transform_db($fieldArray[$field], $rteHtmlParser); + } + } + + /** + * Returns the current backend user authentication instance. + * + * @return BackendUserAuthentication + */ + private function getBackendUser(): BackendUserAuthentication + { + return $GLOBALS['BE_USER']; + } + + /** + * Use columns overrides for evaluation. + * + * Fetch the TCA ["config"] part for a specific field, including the columnsOverrides value. + * Used for checkValue purposes currently (as it takes the checkValue_currentRecord value). + * + * @param \TYPO3\CMS\Core\DataHandling\DataHandler $dataHandler + * @param string $table + * @param string $field + * + * @return array + * + * @see \TYPO3\CMS\Core\DataHandling\DataHandler::resolveFieldConfigurationAndRespectColumnsOverrides + */ + private function resolveFieldConfigurationAndRespectColumnsOverrides( + \TYPO3\CMS\Core\DataHandling\DataHandler $dataHandler, + string $table, + string $field + ): array { + $tcaFieldConf = $GLOBALS['TCA'][$table]['columns'][$field]['config']; + $recordType = BackendUtility::getTCAtypeValue($table, $dataHandler->checkValue_currentRecord); + + $columnsOverridesConfigOfField = $GLOBALS['TCA'][$table]['types'][$recordType]['columnsOverrides'][$field]['config'] ?? null; + + if ($columnsOverridesConfigOfField) { + ArrayUtility::mergeRecursiveWithOverrule($tcaFieldConf, $columnsOverridesConfigOfField); + } + + return $tcaFieldConf; + } +} diff --git a/Classes/Database/RteImagesDbHook.php b/Classes/Database/RteImagesDbHook.php index f10f3e3..3400e2b 100644 --- a/Classes/Database/RteImagesDbHook.php +++ b/Classes/Database/RteImagesDbHook.php @@ -36,6 +36,7 @@ use TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Configuration\BackendConfigurationManager; +use TYPO3\CMS\Extbase\Utility\DebuggerUtility; use function count; use function is_array; @@ -85,6 +86,9 @@ public function __construct( * This method is called to transform RTE content in the database so the Rich Text Editor * can deal with, e.g. links. * + * In detail, the relative image path is changed to an absolute image path before the data is + * displayed in the RTE text box. + * * @param string $value * @param RteHtmlParser $rteHtmlParser * @@ -96,57 +100,66 @@ public function transform_rte( string $value, RteHtmlParser $rteHtmlParser ): string { +////DebuggerUtility::var_dump(__METHOD__); + // Split content by tags and traverse the resulting array for processing: $imgSplit = $rteHtmlParser->splitTags('img', $value); - if (count($imgSplit) > 1) { - $siteUrl = GeneralUtility::getIndpEnv('TYPO3_SITE_URL'); - $siteHost = GeneralUtility::getIndpEnv('TYPO3_REQUEST_HOST'); - $sitePath = ''; + if (count($imgSplit) === 0) { + return $value; + } - if (!is_string($siteUrl)) { - $siteUrl = ''; - } + $siteUrl = GeneralUtility::getIndpEnv('TYPO3_SITE_URL'); + $siteHost = GeneralUtility::getIndpEnv('TYPO3_REQUEST_HOST'); + $sitePath = ''; - if (is_string($siteHost)) { - $sitePath = str_replace( - $siteHost, - '', - $siteUrl - ); - } + if (!is_string($siteUrl)) { + $siteUrl = ''; + } - foreach ($imgSplit as $key => $v) { - // Image found - if (($key % 2) === 1) { - // Get the attributes of the img tag - [$attribArray] = $rteHtmlParser->get_tag_attributes($v, true); - $absoluteUrl = trim($attribArray['src']); - - // Transform the src attribute into an absolute url, if it not already - if (strncasecmp($absoluteUrl, 'http', 4) !== 0) { - // If site is in a sub path (e.g. /~user_jim/) this path needs to be - // removed because it will be added with $siteUrl - $attribArray['src'] = preg_replace( - '#^' . preg_quote($sitePath, '#') . '#', - '', - $attribArray['src'] - ); - - $attribArray['src'] = $siteUrl . $attribArray['src']; - } + if (is_string($siteHost)) { + $sitePath = str_replace( + $siteHost, + '', + $siteUrl + ); + } - // Must have alt attribute - if (!isset($attribArray['alt'])) { - $attribArray['alt'] = ''; - } + foreach ($imgSplit as $key => $v) { + // Odd numbers contains the tags + if (($key % 2) === 1) { + // Get the attributes of the tag + [$attribArray] = $rteHtmlParser->get_tag_attributes($v, true); + + // TODO Why is an absolute URL used here? Is this still required? + $absoluteUrl = trim($attribArray['src']); + + // Transform the src attribute into an absolute url, if it not already + if (strncasecmp($absoluteUrl, 'http', 4) !== 0) { + // If site is in a sub path (e.g. /~user_jim/) this path needs to be + // removed because it will be added with $siteUrl + $attribArray['src'] = preg_replace( + '#^' . preg_quote($sitePath, '#') . '#', + '', + $attribArray['src'] + ); - $imgSplit[$key] = ''; + $attribArray['src'] = $siteUrl . $attribArray['src']; } + + // Must have alt attribute + if (!isset($attribArray['alt'])) { + $attribArray['alt'] = ''; + } + + $imgSplit[$key] = ''; } } + +////DebuggerUtility::var_dump($imgSplit); + // Return processed content: return implode('', $imgSplit); } @@ -167,244 +180,270 @@ public function transform_db( string $value, RteHtmlParser $rteHtmlParser ): string { + +////DebuggerUtility::var_dump(__METHOD__); +////DebuggerUtility::var_dump($value); +//exit; + // Split content by tags and traverse the resulting array for processing: $imgSplit = $rteHtmlParser->splitTags('img', $value); - if (count($imgSplit) > 1) { - $siteUrl = GeneralUtility::getIndpEnv('TYPO3_SITE_URL'); - $siteHost = GeneralUtility::getIndpEnv('TYPO3_REQUEST_HOST'); - $sitePath = ''; + if (count($imgSplit) === 0) { + return $value; + } - if (!is_string($siteUrl)) { - $siteUrl = ''; - } + $siteUrl = GeneralUtility::getIndpEnv('TYPO3_SITE_URL'); + $siteHost = GeneralUtility::getIndpEnv('TYPO3_REQUEST_HOST'); + $sitePath = ''; + $siteUrl = ''; +//DebuggerUtility::var_dump(__METHOD__); +//DebuggerUtility::var_dump($siteUrl); +//DebuggerUtility::var_dump($siteHost); - if (is_string($siteHost)) { - $sitePath = str_replace( - $siteHost, - '', - $siteUrl - ); - } + if (!is_string($siteUrl)) { + $siteUrl = ''; + } + + if (is_string($siteHost)) { + $sitePath = str_replace( + $siteHost, + '', + $siteUrl + ); + } - $resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class); - $magicImageService = GeneralUtility::makeInstance(MagicImageService::class); - $backendConfigurationManager = GeneralUtility::makeInstance(BackendConfigurationManager::class); +//DebuggerUtility::var_dump($sitePath); - $pageId = $backendConfigurationManager->getDefaultBackendStoragePid(); - $rootLine = BackendUtility::BEgetRootLine($pageId); + $resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class); + $magicImageService = GeneralUtility::makeInstance(MagicImageService::class); + $backendConfigurationManager = GeneralUtility::makeInstance(BackendConfigurationManager::class); - $loader = GeneralUtility::makeInstance(PageTsConfigLoader::class); - $cacheManager = GeneralUtility::makeInstance(CacheManager::class); - $typoScriptParser = GeneralUtility::makeInstance(TypoScriptParser::class); + $pageId = $backendConfigurationManager->getDefaultBackendStoragePid(); + $rootLine = BackendUtility::BEgetRootLine($pageId); - $tsConfigString = $loader->load($rootLine); + $loader = GeneralUtility::makeInstance(PageTsConfigLoader::class); + $cacheManager = GeneralUtility::makeInstance(CacheManager::class); + $typoScriptParser = GeneralUtility::makeInstance(TypoScriptParser::class); - // Parse the PageTS into an array, also applying conditions + $tsConfigString = $loader->load($rootLine); - $parser = GeneralUtility::makeInstance( - PageTsConfigParser::class, - $typoScriptParser, - $cacheManager->getCache('hash') - ); + // Parse the PageTS into an array, also applying conditions - $matcher = GeneralUtility::makeInstance(ConditionMatcher::class, null, $pageId, $rootLine); - - $tsConfig = $parser->parse($tsConfigString, $matcher); - $magicImageService->setMagicImageMaximumDimensions($tsConfig['RTE.']['default.']); - - foreach ($imgSplit as $key => $v) { - // Image found, do processing: - if (($key % 2) === 1) { - // Get attributes - [$attribArray] = $rteHtmlParser->get_tag_attributes($v, true); - // It's always an absolute URL coming from the RTE into the Database. - $absoluteUrl = trim($attribArray['src']); - // Make path absolute if it is relative, and we have a site path which is not '/' - $pI = pathinfo($absoluteUrl); - - if (($sitePath !== '') && GeneralUtility::isFirstPartOfStr($absoluteUrl, $sitePath)) { - // If site is in a subpath (e.g. /~user_jim/) this path needs to be removed - // because it will be added with $siteUrl - $absoluteUrl = substr($absoluteUrl, strlen($sitePath)); - $absoluteUrl = $siteUrl . $absoluteUrl; - } + $parser = GeneralUtility::makeInstance( + PageTsConfigParser::class, + $typoScriptParser, + $cacheManager->getCache('hash') + ); - // Get image dimensions set in the image tag, if any - $imageWidth = $this->getImageWidthFromAttributes($attribArray); - $imageHeight = $this->getImageHeightFromAttributes($attribArray); + $matcher = GeneralUtility::makeInstance(ConditionMatcher::class, null, $pageId, $rootLine); - if ($imageWidth > 0) { - $attribArray['width'] = $imageWidth; - } + $tsConfig = $parser->parse($tsConfigString, $matcher); + $magicImageService->setMagicImageMaximumDimensions($tsConfig['RTE.']['default.']); - if ($imageHeight > 0) { - $attribArray['height'] = $imageHeight; - } + foreach ($imgSplit as $key => $v) { + // Odd numbers contains the tags + if (($key % 2) === 1) { + // Get attributes + [$attribArray] = $rteHtmlParser->get_tag_attributes($v, true); - $originalImageFile = null; - if (isset($attribArray['data-htmlarea-file-uid'])) { - // An original image file uid is available - try { - $originalImageFile = $resourceFactory - ->getFileObject((int) $attribArray['data-htmlarea-file-uid'] . '0'); - } catch (FileDoesNotExistException $exception) { - if ($this->logger !== null) { - // Log the fact the file could not be retrieved. - $message = sprintf( - 'Could not find file with uid "%s"', - $attribArray['data-htmlarea-file-uid'] - ); - - $this->logger->error($message, ['exception' => $exception]); - } + // It's always an absolute URL coming from the RTE into the Database. + $absoluteUrl = trim($attribArray['src']); + +//DebuggerUtility::var_dump($absoluteUrl); + + // Make path absolute if it is relative, and we have a site path which is not '/' + if (($sitePath !== '') && GeneralUtility::isFirstPartOfStr($absoluteUrl, $sitePath)) { + // If site is in a subpath (e.g. /~user_jim/) this path needs to be removed + // because it will be added with $siteUrl + $absoluteUrl = substr($absoluteUrl, strlen($sitePath)); + $absoluteUrl = $siteUrl . $absoluteUrl; + } + +//DebuggerUtility::var_dump($absoluteUrl); +// exit; + // Get image dimensions set in the image tag, if any + $imageWidth = $this->getImageWidthFromAttributes($attribArray); + $imageHeight = $this->getImageHeightFromAttributes($attribArray); + + if ($imageWidth > 0) { + $attribArray['width'] = $imageWidth; + } + + if ($imageHeight > 0) { + $attribArray['height'] = $imageHeight; + } + + $originalImageFile = null; + if (isset($attribArray['data-htmlarea-file-uid'])) { + // An original image file uid is available + try { + $originalImageFile = $resourceFactory + ->getFileObject((int) $attribArray['data-htmlarea-file-uid'] . '0'); + } catch (FileDoesNotExistException $exception) { + if ($this->logger !== null) { + // Log the fact the file could not be retrieved. + $message = sprintf( + 'Could not find file with uid "%s"', + $attribArray['data-htmlarea-file-uid'] + ); + + $this->logger->error($message, ['exception' => $exception]); } } + } - $isBackend = false; +//DebuggerUtility::var_dump('XX'); +//DebuggerUtility::var_dump($originalImageFile); +//exit; - // Determine application type - if (($GLOBALS['TYPO3_REQUEST'] ?? null) instanceof ServerRequestInterface) { - $isBackend = ApplicationType::fromRequest($GLOBALS['TYPO3_REQUEST'])->isBackend(); - } + $isBackend = false; - if ($originalImageFile instanceof File) { - // Build public URL to image, remove trailing slash from site URL - $imageFileUrl = rtrim($siteUrl, '/') . $originalImageFile->getPublicUrl(); + // Determine application type + if (($GLOBALS['TYPO3_REQUEST'] ?? null) instanceof ServerRequestInterface) { + $isBackend = ApplicationType::fromRequest($GLOBALS['TYPO3_REQUEST'])->isBackend(); + } + + if ($originalImageFile instanceof File) { + // Build public URL to image, remove trailing slash from site URL + $imageFileUrl = rtrim($siteUrl, '/') . $originalImageFile->getPublicUrl(); + + // Public url of local file is relative to the site url, absolute otherwise + if (($absoluteUrl !== $imageFileUrl) && ($absoluteUrl !== $originalImageFile->getPublicUrl())) { + // Magic image case: get a processed file with the requested configuration + $imageConfiguration = [ + 'width' => $imageWidth, + 'height' => $imageHeight, + ]; + + $magicImage = $magicImageService + ->createMagicImage($originalImageFile, $imageConfiguration); + + $attribArray['width'] = $magicImage->getProperty('width'); + $attribArray['height'] = $magicImage->getProperty('height'); + + $imgSrc = $magicImage->getPublicUrl(); + + // publicUrl like 'https://www.domain.xy/typo3/image/process?token=...'? + // -> generate img source from storage base path and identifier instead + if (($imgSrc !== null) && (strpos($imgSrc, 'process?token=') !== false)) { + $storageBasePath = $magicImage->getStorage() !== null + ? $magicImage->getStorage()->getConfiguration()['basePath'] + : ''; + + if ($storageBasePath !== '') { + $imgUrlPre = ($storageBasePath[strlen($storageBasePath) - 1] === '/') + ? substr($storageBasePath, 0, -1) + : $storageBasePath; + } else { + $imgUrlPre = ''; + } - // Public url of local file is relative to the site url, absolute otherwise - if (($absoluteUrl !== $imageFileUrl) && ($absoluteUrl !== $originalImageFile->getPublicUrl())) { - // Magic image case: get a processed file with the requested configuration + $imgSrc = '/' . $imgUrlPre . $magicImage->getIdentifier(); + } +//DebuggerUtility::var_dump(__METHOD__); +//DebuggerUtility::var_dump($imgSrc); +//exit; + $attribArray['src'] = $imgSrc; + } + } elseif ( + !($this->procOptions['dontFetchExtPictures'] ?? false) + && $this->fetchExternalImages + && $isBackend + && !GeneralUtility::isFirstPartOfStr($absoluteUrl, $siteUrl) + ) { + // External image from another URL: in that case, fetch image, unless + // the feature is disabled, or we are not in backend mode. + // + // Fetch the external image + $externalFile = null; + try { + $externalFile = GeneralUtility::getUrl($absoluteUrl); + } catch (Throwable $e) { + // do nothing, further image processing will be skipped + } + if ($externalFile !== null) { + $pU = parse_url($absoluteUrl); + $path = is_array($pU) ? ($pU['path'] ?? '') : ''; + $pI = pathinfo($path); + $extension = strtolower($pI['extension'] ?? ''); + + if ( + $extension === 'jpg' + || $extension === 'jpeg' + || $extension === 'gif' + || $extension === 'png' + ) { + $fileName = GeneralUtility::shortMD5($absoluteUrl) . '.' . ($pI['extension'] ?? ''); + // We insert this image into the user default upload folder + $folder = $GLOBALS['BE_USER']->getDefaultUploadFolder(); + $fileObject = $folder->createFile($fileName)->setContents($externalFile); $imageConfiguration = [ - 'width' => $imageWidth, - 'height' => $imageHeight, + 'width' => $attribArray['width'], + 'height' => $attribArray['height'] ]; $magicImage = $magicImageService - ->createMagicImage($originalImageFile, $imageConfiguration); + ->createMagicImage($fileObject, $imageConfiguration); $attribArray['width'] = $magicImage->getProperty('width'); $attribArray['height'] = $magicImage->getProperty('height'); - - $imgSrc = $magicImage->getPublicUrl(); - - // publicUrl like 'https://www.domain.xy/typo3/image/process?token=...'? - // -> generate img source from storage basepath and identifier instead - if ($imgSrc !== null && strpos($imgSrc, 'process?token=') !== false) { - $storageBasePath = $magicImage->getStorage() !== null - ? $magicImage->getStorage()->getConfiguration()['basePath'] - : ''; - - if ($storageBasePath !== '') { - $imgUrlPre = ($storageBasePath[strlen($storageBasePath) - 1] === '/') - ? substr($storageBasePath, 0, -1) - : $storageBasePath; - } else { - $imgUrlPre = ''; - } - - $imgSrc = '/' . $imgUrlPre . $magicImage->getIdentifier(); - } - - $attribArray['src'] = $imgSrc; + $attribArray['data-htmlarea-file-uid'] = $fileObject->getUid(); + $attribArray['src'] = $magicImage->getPublicUrl(); } - } elseif ( - !($this->procOptions['dontFetchExtPictures'] ?? false) - && $this->fetchExternalImages - && $isBackend - && !GeneralUtility::isFirstPartOfStr($absoluteUrl, $siteUrl) - ) { - // External image from another URL: in that case, fetch image, unless - // the feature is disabled, or we are not in backend mode. - // - // Fetch the external image - $externalFile = null; + } + } elseif (GeneralUtility::isFirstPartOfStr($absoluteUrl, $siteUrl)) { + // Finally, check image as local file (siteURL equals the one of the image) + // Image has no data-htmlarea-file-uid attribute + // Relative path, rawurldecoded for special characters. + $path = rawurldecode(substr($absoluteUrl, strlen($siteUrl))); + // Absolute filepath, locked to relative path of this project + $filepath = GeneralUtility::getFileAbsFileName($path); + // Check file existence (in relative directory to this installation!) + if (($filepath !== '') && @is_file($filepath)) { + // Let's try to find a file uid for this image try { - $externalFile = GeneralUtility::getUrl($absoluteUrl); - } catch (Throwable $e) { - // do nothing, further image processing will be skipped - } - if ($externalFile !== null) { - $pU = parse_url($absoluteUrl); - $path = is_array($pU) ? ($pU['path'] ?? '') : ''; - $pI = pathinfo($path); - $extension = strtolower($pI['extension'] ?? ''); - - if ( - $extension === 'jpg' - || $extension === 'jpeg' - || $extension === 'gif' - || $extension === 'png' - ) { - $fileName = GeneralUtility::shortMD5($absoluteUrl) . '.' . ($pI['extension'] ?? ''); - // We insert this image into the user default upload folder - $folder = $GLOBALS['BE_USER']->getDefaultUploadFolder(); - $fileObject = $folder->createFile($fileName)->setContents($externalFile); - $imageConfiguration = [ - 'width' => $attribArray['width'], - 'height' => $attribArray['height'] - ]; - - $magicImage = $magicImageService - ->createMagicImage($fileObject, $imageConfiguration); - - $attribArray['width'] = $magicImage->getProperty('width'); - $attribArray['height'] = $magicImage->getProperty('height'); - $attribArray['data-htmlarea-file-uid'] = $fileObject->getUid(); - $attribArray['src'] = $magicImage->getPublicUrl(); - } - } - } elseif (GeneralUtility::isFirstPartOfStr($absoluteUrl, $siteUrl)) { - // Finally, check image as local file (siteURL equals the one of the image) - // Image has no data-htmlarea-file-uid attribute - // Relative path, rawurldecoded for special characters. - $path = rawurldecode(substr($absoluteUrl, strlen($siteUrl))); - // Absolute filepath, locked to relative path of this project - $filepath = GeneralUtility::getFileAbsFileName($path); - // Check file existence (in relative directory to this installation!) - if (($filepath !== '') && @is_file($filepath)) { - // Let's try to find a file uid for this image - try { - $fileOrFolderObject = $resourceFactory->retrieveFileOrFolderObject($path); - if ($fileOrFolderObject instanceof FileInterface) { - $fileIdentifier = $fileOrFolderObject->getIdentifier(); - $fileObject = $fileOrFolderObject->getStorage()->getFile($fileIdentifier); - if ($fileObject instanceof AbstractFile) { - $fileUid = $fileObject->getUid(); - // if the retrieved file is a processed file, get the original file... - if ($fileObject->hasProperty('original')) { - $fileUid = $fileObject->getProperty('original'); - } - $attribArray['data-htmlarea-file-uid'] = $fileUid; + $fileOrFolderObject = $resourceFactory->retrieveFileOrFolderObject($path); + if ($fileOrFolderObject instanceof FileInterface) { + $fileIdentifier = $fileOrFolderObject->getIdentifier(); + $fileObject = $fileOrFolderObject->getStorage()->getFile($fileIdentifier); + if ($fileObject instanceof AbstractFile) { + $fileUid = $fileObject->getUid(); + // if the retrieved file is a processed file, get the original file... + if ($fileObject->hasProperty('original')) { + $fileUid = $fileObject->getProperty('original'); } + $attribArray['data-htmlarea-file-uid'] = $fileUid; } - } catch (ResourceDoesNotExistException $resourceDoesNotExistException) { - // Nothing to be done if file/folder not found } + } catch (ResourceDoesNotExistException $resourceDoesNotExistException) { + // Nothing to be done if file/folder not found } } - if (!$attribArray) { - // some error occurred, leave the img tag as is - continue; - } - // Remove width and height from style attribute - $attribArray['style'] = preg_replace( - '/(?:^|[^-])(\\s*(?:width|height)\\s*:[^;]*(?:$|;))/si', - '', - $attribArray['style'] ?? '' - ); - // Must have alt attribute - if (!isset($attribArray['alt'])) { - $attribArray['alt'] = ''; - } - // Convert absolute to relative url - if (GeneralUtility::isFirstPartOfStr($attribArray['src'], $siteUrl)) { - $attribArray['src'] = substr($attribArray['src'], strlen($siteUrl)); - } - $imgSplit[$key] = ''; } + if (!$attribArray) { + // some error occurred, leave the img tag as is + continue; + } + // Remove width and height from style attribute + $attribArray['style'] = preg_replace( + '/(?:^|[^-])(\\s*(?:width|height)\\s*:[^;]*(?:$|;))/si', + '', + $attribArray['style'] ?? '' + ); + // Must have alt attribute + if (!isset($attribArray['alt'])) { + $attribArray['alt'] = ''; + } + // Convert absolute to relative url + if (GeneralUtility::isFirstPartOfStr($attribArray['src'], $siteUrl)) { + $attribArray['src'] = substr($attribArray['src'], strlen($siteUrl)); + } + $imgSplit[$key] = ''; } } + +//exit; + return implode('', $imgSplit); } diff --git a/Classes/FormDataProvider/TransformRteDataProvider.php b/Classes/FormDataProvider/TransformRteDataProvider.php new file mode 100644 index 0000000..4a73be2 --- /dev/null +++ b/Classes/FormDataProvider/TransformRteDataProvider.php @@ -0,0 +1,80 @@ + + * @license https://www.gnu.org/licenses/agpl-3.0.de.html + * @link https://www.netresearch.de + */ +class TransformRteDataProvider implements FormDataProviderInterface +{ + /** + * Add transformed RTE text into $result data array. + * + * @param array $result Initialized result array + * + * @return array Result filled with more data + */ + public function addData(array $result): array + { + foreach ($result['processedTca']['columns'] as $fieldName => $fieldConfig) { + // Handle only fields of type "text" + if (empty($fieldConfig['config']['type']) + || ($fieldConfig['config']['type'] !== 'text') + ) { + continue; + } + + // Ignore all none RTE text fields + if (!isset($fieldConfig['config']['enableRichtext']) + || ($fieldConfig['config']['enableRichtext'] === false) + || ($this->getBackendUser()->isRTE() === false) + ) { + continue; + } + + // Ignore empty fields + if ($result['databaseRow'][$fieldName] === null) { + continue; + } + + $rteImageDbHook = GeneralUtility::makeInstance(RteImagesDbHook::class); + $rteHtmlParser = GeneralUtility::makeInstance(RteHtmlParser::class); + + $result['databaseRow'][$fieldName] = $rteImageDbHook->transform_rte( + $result['databaseRow'][$fieldName], + $rteHtmlParser + ); + } + + return $result; + } + + /** + * Returns the current backend user authentication instance. + * + * @return BackendUserAuthentication + */ + private function getBackendUser(): BackendUserAuthentication + { + return $GLOBALS['BE_USER']; + } +} diff --git a/Configuration/Backend/Routes.php b/Configuration/Backend/Routes.php index 01260c7..7a7c429 100755 --- a/Configuration/Backend/Routes.php +++ b/Configuration/Backend/Routes.php @@ -7,6 +7,8 @@ * LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + /** * Definitions of routes */ diff --git a/Configuration/RTE/Plugin.yaml b/Configuration/RTE/Plugin.yaml index a585466..d99b320 100755 --- a/Configuration/RTE/Plugin.yaml +++ b/Configuration/RTE/Plugin.yaml @@ -1,4 +1,4 @@ -# Register image plugin for ckeditor +## Register image plugin for ckeditor editor: - externalPlugins: - typo3image: { resource: "EXT:rte_ckeditor_image/Resources/Public/JavaScript/Plugins/typo3image.js", route: "rteckeditorimage_wizard_select_image" } + externalPlugins: + typo3image: { resource: "EXT:rte_ckeditor_image/Resources/Public/JavaScript/Plugins/typo3image.js", route: "rteckeditorimage_wizard_select_image" } diff --git a/Resources/Public/JavaScript/Plugins/typo3image.js b/Resources/Public/JavaScript/Plugins/typo3image.js index b9c51fd..a7c7071 100644 --- a/Resources/Public/JavaScript/Plugins/typo3image.js +++ b/Resources/Public/JavaScript/Plugins/typo3image.js @@ -63,60 +63,62 @@ // Update image when editor loads if (existingImages.length) { - $.each(existingImages, function(i,curImg) { - var $curImg = $(curImg), - uid = $curImg.attr('data-htmlarea-file-uid'), - table = $curImg.attr('data-htmlarea-file-table'), - routeUrl = editor.config.typo3image.routeUrl, - url = routeUrl - + (routeUrl.indexOf('?') === -1 ? '?' : '&') - + 'action=info' - + '&fileId=' + encodeURIComponent(uid) - + '&table=' + encodeURIComponent(table); - - if (typeof $curImg.attr('width') !== 'undefined' && $curImg.attr('width').length) { - url += '&P[width]=' + $curImg.attr('width'); - } - - if (typeof $curImg.attr('height') !== 'undefined' && $curImg.attr('height').length) { - url += '&P[height]=' + $curImg.attr('height'); - } - - $.getJSON(url, function(newImg) { - // RTEs in flexforms might contain dots in their ID, so we need to escape them - var escapedEditorId = editor.element.$.id.replace('.', '\\.'); - - var realEditor = $('#cke_' + escapedEditorId).find('iframe').contents().find('body'), - newImgUrl = newImg.processed.url || newImg.url, - imTag = realEditor.contents().find('img[data-htmlarea-file-uid='+uid+']'); - - // Sets the title attribute if any - if (typeof $curImg.attr('title') !== 'undefined' && $curImg.attr('title').length) { - imTag.attr('title', $curImg.attr('title')); - } + editor.on( 'contentDom', function() { + $.each(existingImages, function(i,curImg) { + var $curImg = $(curImg), + uid = $curImg.attr('data-htmlarea-file-uid'), + table = $curImg.attr('data-htmlarea-file-table'), + routeUrl = editor.config.typo3image.routeUrl, + url = routeUrl + + (routeUrl.indexOf('?') === -1 ? '?' : '&') + + 'action=info' + + '&fileId=' + encodeURIComponent(uid) + + '&table=' + encodeURIComponent(table); - // Sets the width attribute if any if (typeof $curImg.attr('width') !== 'undefined' && $curImg.attr('width').length) { - imTag.attr('width', $curImg.attr('width')); + url += '&P[width]=' + $curImg.attr('width'); } - // Sets the height attribute if any if (typeof $curImg.attr('height') !== 'undefined' && $curImg.attr('height').length) { - imTag.attr('height', $curImg.attr('height')); + url += '&P[height]=' + $curImg.attr('height'); } - // Sets the style attribute if any - if (typeof $curImg.attr('style') !== 'undefined' && $curImg.attr('style').length) { - imTag.attr('style', $curImg.attr('style')); - } + $.getJSON(url, function(newImg) { + // RTEs in flexforms might contain dots in their ID, so we need to escape them + var escapedEditorId = editor.element.$.id.replace('.', '\\.'); - // Replaces the current html with the updated one - realEditor.html(realEditor.html()); + var realEditor = $('#cke_' + escapedEditorId).find('iframe').contents().find('body'), + newImgUrl = newImg.processed.url || newImg.url, + imTag = realEditor.contents().find('img[data-htmlarea-file-uid='+uid+']'); - // Replace current url with updated one - if ($curImg.attr('src') && newImgUrl) { - realEditor.html(realEditor.html().replaceAll($curImg.attr('src'), newImgUrl)); - } + // Sets the title attribute if any + if (typeof $curImg.attr('title') !== 'undefined' && $curImg.attr('title').length) { + imTag.attr('title', $curImg.attr('title')); + } + + // Sets the width attribute if any + if (typeof $curImg.attr('width') !== 'undefined' && $curImg.attr('width').length) { + imTag.attr('width', $curImg.attr('width')); + } + + // Sets the height attribute if any + if (typeof $curImg.attr('height') !== 'undefined' && $curImg.attr('height').length) { + imTag.attr('height', $curImg.attr('height')); + } + + // Sets the style attribute if any + if (typeof $curImg.attr('style') !== 'undefined' && $curImg.attr('style').length) { + imTag.attr('style', $curImg.attr('style')); + } + + // Replaces the current html with the updated one + realEditor.html(realEditor.html()); + + // Replace current url with updated one + if ($curImg.attr('src') && newImgUrl) { + realEditor.html(realEditor.html().replaceAll($curImg.attr('src'), newImgUrl)); + } + }); }); }); } @@ -212,7 +214,7 @@ }); /** - * + * * @returns value */ function getTitleText() { @@ -225,7 +227,7 @@ } /** - * + * * @param url * @return relativeUrl */ @@ -245,7 +247,7 @@ return "/" + url; } } - + return url; } @@ -386,7 +388,7 @@ + '&bparams=' + bparams.join('|'), deferred = $.Deferred(), $modal; - +console.log(url); require(['TYPO3/CMS/Backend/Modal'], function (Modal) { $modal = Modal.advanced({ type: Modal.types.iframe, diff --git a/ext_localconf.php b/ext_localconf.php index aaf22b0..e058eb5 100755 --- a/ext_localconf.php +++ b/ext_localconf.php @@ -11,12 +11,21 @@ defined('TYPO3_MODE') or die(); -$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_parsehtml_proc.php']['transformation']['rtehtmlarea_images_db'] - = \Netresearch\RteCKEditorImage\Database\RteImagesDbHook::class; - \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addPageTSConfig( 'RTE.default.proc.overruleMode := addToList(default)' ); \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addPageTSConfig( 'RTE.default.proc.overruleMode := addToList(rtehtmlarea_images_db)' ); + +// Process the text inserted into TCA text field before the core processing tags place +$GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['formDataGroup']['tcaDatabaseRecord'] + [\Netresearch\RteCKEditorImage\FormDataProvider\TransformRteDataProvider::class] = [ + 'before' => [ + \TYPO3\CMS\Backend\Form\FormDataProvider\TcaText::class, + ] + ]; + +// Process the modified text from TCA text field before its stored in the database +$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass'][] + = \Netresearch\RteCKEditorImage\DataHandling\DataHandler::class;