-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[FEATURE] Add CroppedImage and CroppedImageUri ViewHelpers
- Loading branch information
1 parent
f8977fd
commit c05b9fb
Showing
5 changed files
with
335 additions
and
152 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
<?php | ||
declare(strict_types=1); | ||
namespace Smichaelsen\MelonImages\Service; | ||
|
||
use TYPO3\CMS\Backend\Utility\BackendUtility; | ||
use TYPO3\CMS\Core\Imaging\ImageManipulation\Area; | ||
use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection; | ||
use TYPO3\CMS\Core\Resource\FileReference; | ||
use TYPO3\CMS\Core\SingletonInterface; | ||
use TYPO3\CMS\Core\TypoScript\TypoScriptService; | ||
use TYPO3\CMS\Core\Utility\GeneralUtility; | ||
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface; | ||
use TYPO3\CMS\Extbase\Object\ObjectManager; | ||
use TYPO3\CMS\Extbase\Service\ImageService; | ||
|
||
class ImageDataProvider implements SingletonInterface | ||
{ | ||
|
||
/** | ||
* @var ImageService | ||
*/ | ||
protected $imageService; | ||
|
||
public function injectImageService(ImageService $imageService) | ||
{ | ||
$this->imageService = $imageService; | ||
} | ||
|
||
public function getImageVariantData(FileReference $fileReference, string $variant) | ||
{ | ||
$cropConfiguration = json_decode((string)$fileReference->getProperty('crop'), true); | ||
// filter for all crop configurations that match the chosen image variant | ||
$matchingCropConfiguration = array_filter($cropConfiguration, function ($key) use ($variant) { | ||
return strpos($key, $variant . '__') === 0; | ||
}, ARRAY_FILTER_USE_KEY); | ||
$cropVariants = CropVariantCollection::create(json_encode($matchingCropConfiguration)); | ||
$cropVariantIds = array_keys($matchingCropConfiguration); | ||
unset($matchingCropConfiguration); | ||
|
||
$sources = []; | ||
$pixelDensities = $this->getPixelDensitiesFromTypoScript(); | ||
foreach ($cropVariantIds as $cropVariantId) { | ||
$srcset = []; | ||
$sizeConfiguration = $this->getSizeConfiguration($fileReference, $cropVariantId); | ||
|
||
foreach ($pixelDensities as $pixelDensity) { | ||
$imageUri = $this->processImage( | ||
$fileReference, | ||
(int)round(($sizeConfiguration['width'] * $pixelDensity)), | ||
(int)round(($sizeConfiguration['height'] * $pixelDensity)), | ||
$cropVariants->getCropArea($cropVariantId) | ||
); | ||
$srcset[] = $imageUri . ' ' . $pixelDensity . 'x'; | ||
} | ||
|
||
$mediaQuery = $this->getMediaQueryFromSizeConfig($sizeConfiguration); | ||
|
||
$sources[] = [ | ||
'srcsets' => $srcset, | ||
'mediaQuery' => $mediaQuery | ||
]; | ||
} | ||
|
||
$lastCropVariantId = end($cropVariantIds); | ||
$sizeConfiguration = $this->getSizeConfiguration($fileReference, $lastCropVariantId); | ||
$defaultImageUri = $imageUri = $this->processImage( | ||
$fileReference, | ||
(int)$sizeConfiguration['width'], | ||
(int)$sizeConfiguration['height'], | ||
$cropVariants->getCropArea($lastCropVariantId) | ||
); | ||
|
||
return [ | ||
'sources' => $sources, | ||
'fallbackImageSrc' => $defaultImageUri, | ||
]; | ||
} | ||
|
||
protected function processImage( | ||
FileReference $fileReference, | ||
int $width, | ||
int $height, | ||
$cropArea = null | ||
): string { | ||
if ($cropArea instanceof Area && !$cropArea->isEmpty()) { | ||
$cropArea = $cropArea->makeAbsoluteBasedOnFile($fileReference); | ||
} else { | ||
$cropArea = null; | ||
} | ||
$processingInstructions = [ | ||
'width' => $width, | ||
'height' => $height, | ||
'crop' => $cropArea, | ||
]; | ||
$processedImage = $this->imageService->applyProcessingInstructions($fileReference, $processingInstructions); | ||
return $this->imageService->getImageUri($processedImage); | ||
} | ||
|
||
protected function getSizeConfiguration(FileReference $fileReference, string $cropVariantId): array | ||
{ | ||
list($variantIdentifier, $sizeIdentifier) = explode('__', $cropVariantId); | ||
return $this->getMelonImagesConfigForFileReference($fileReference)['variants'][$variantIdentifier]['sizes'][$sizeIdentifier]; | ||
} | ||
|
||
protected function getBreakpointsFromTypoScript(): array | ||
{ | ||
return (array)$this->getTypoScriptSettings()['breakpoints']; | ||
} | ||
|
||
protected function getPixelDensitiesFromTypoScript(): array | ||
{ | ||
return GeneralUtility::trimExplode(',', (string)$this->getTypoScriptSettings()['pixelDensities'] ?? '1'); | ||
} | ||
|
||
protected function getTypoScriptSettings(): array | ||
{ | ||
static $typoScriptSettings; | ||
if (!is_array($typoScriptSettings)) { | ||
$objectManager = GeneralUtility::makeInstance(ObjectManager::class); | ||
$configurationManager = $objectManager->get(ConfigurationManagerInterface::class); | ||
$typoscript = $configurationManager->getConfiguration( | ||
ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT | ||
); | ||
$typoScriptSettings = GeneralUtility::makeInstance(TypoScriptService::class)->convertTypoScriptArrayToPlainArray( | ||
$typoscript['package.']['Smichaelsen\MelonImages.'] ?? [] | ||
); | ||
} | ||
return $typoScriptSettings; | ||
} | ||
|
||
protected function getMediaQueryFromSizeConfig(array $sizeConfiguration): string | ||
{ | ||
$breakpointsConfig = $this->getBreakpointsFromTypoScript(); | ||
$breakpoints = []; | ||
foreach (GeneralUtility::trimExplode(',', $sizeConfiguration['breakpoints']) as $breakpointName) { | ||
$constraints = []; | ||
if ($breakpointsConfig[$breakpointName]['from']) { | ||
$constraints[] = '(min-width: ' . $breakpointsConfig[$breakpointName]['from'] . 'px)'; | ||
} | ||
if ($breakpointsConfig[$breakpointName]['to']) { | ||
$constraints[] = '(max-width: ' . $breakpointsConfig[$breakpointName]['to'] . 'px)'; | ||
} | ||
if (empty($constraints)) { | ||
continue; | ||
} | ||
$breakpoints[] = implode(' and ', $constraints); | ||
} | ||
if (empty($breakpoints)) { | ||
return ''; | ||
} | ||
return implode(', ', $breakpoints); | ||
} | ||
|
||
protected function getMelonImagesConfigForFileReference(FileReference $fileReference): array | ||
{ | ||
static $melonConfigPerFileReferenceUid = []; | ||
if (!isset($melonConfigPerFileReferenceUid[$fileReference->getUid()])) { | ||
$melonConfigPerFileReferenceUid[$fileReference->getUid()] = (function () use ($fileReference) { | ||
$typoScriptSettings = $this->getTypoScriptSettings(); | ||
$table = $fileReference->getProperty('tablenames'); | ||
$tableSettings = $typoScriptSettings['croppingConfiguration'][$table]; | ||
unset($typoScriptSettings); | ||
$fieldName = $fileReference->getProperty('fieldname'); | ||
$typeField = $GLOBALS['TCA'][$table]['ctrl']['type']; | ||
if ($typeField) { | ||
$record = BackendUtility::getRecord($table, $fileReference->getProperty('uid_foreign'), $typeField); | ||
$type = $record[$typeField]; | ||
if ($tableSettings[$type]) { | ||
return $tableSettings[$type][$fieldName]; | ||
} | ||
} | ||
return $tableSettings['_all'][$fieldName]; | ||
})(); | ||
} | ||
return $melonConfigPerFileReferenceUid[$fileReference->getUid()]; | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
<?php | ||
declare(strict_types=1); | ||
namespace Smichaelsen\MelonImages\ViewHelpers; | ||
|
||
use Smichaelsen\MelonImages\Service\ImageDataProvider; | ||
use TYPO3\CMS\Core\Resource\FileReference; | ||
use TYPO3\CMS\Extbase\Domain\Model\FileReference as ExtbaseFileReferenceModel; | ||
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; | ||
|
||
class CroppedImageUriViewHelper extends AbstractViewHelper | ||
{ | ||
|
||
/** | ||
* @var ImageDataProvider | ||
* | ||
*/ | ||
protected $imageDataProvider; | ||
|
||
public function injectImageDataProvider(ImageDataProvider $imageDataProvider) | ||
{ | ||
$this->imageDataProvider = $imageDataProvider; | ||
} | ||
|
||
public function initializeArguments() | ||
{ | ||
parent::initializeArguments(); | ||
$this->registerArgument('fileReference', 'mixed', 'File reference to render', true); | ||
$this->registerArgument('variant', 'string', 'Name of the image variant to use', true); | ||
} | ||
|
||
public function render(): string | ||
{ | ||
$fileReference = $this->arguments['fileReference']; | ||
if ($fileReference instanceof ExtbaseFileReferenceModel) { | ||
$fileReference = $fileReference->getOriginalResource(); | ||
} | ||
if (!$fileReference instanceof FileReference) { | ||
return ''; | ||
} | ||
|
||
$variant = $this->arguments['variant']; | ||
$variantData = $this->imageDataProvider->getImageVariantData($fileReference, $variant); | ||
|
||
return $variantData['fallbackImageSrc']; | ||
} | ||
|
||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
<?php | ||
declare(strict_types=1); | ||
namespace Smichaelsen\MelonImages\ViewHelpers; | ||
|
||
use Smichaelsen\MelonImages\Service\ImageDataProvider; | ||
use TYPO3\CMS\Core\Resource\FileReference; | ||
use TYPO3\CMS\Extbase\Domain\Model\FileReference as ExtbaseFileReferenceModel; | ||
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper; | ||
|
||
class CroppedImageViewHelper extends AbstractTagBasedViewHelper | ||
{ | ||
|
||
protected $tagName = 'picture'; | ||
|
||
/** | ||
* @var ImageDataProvider | ||
* | ||
*/ | ||
protected $imageDataProvider; | ||
|
||
public function injectImageDataProvider(ImageDataProvider $imageDataProvider) | ||
{ | ||
$this->imageDataProvider = $imageDataProvider; | ||
} | ||
|
||
public function initializeArguments() | ||
{ | ||
parent::initializeArguments(); | ||
$this->registerUniversalTagAttributes(); | ||
$this->registerArgument('fileReference', 'mixed', 'File reference to render', true); | ||
$this->registerArgument('variant', 'string', 'Name of the image variant to use', true); | ||
} | ||
|
||
public function render(): string | ||
{ | ||
$fileReference = $this->arguments['fileReference']; | ||
if ($fileReference instanceof ExtbaseFileReferenceModel) { | ||
$fileReference = $fileReference->getOriginalResource(); | ||
} | ||
if (!$fileReference instanceof FileReference) { | ||
return ''; | ||
} | ||
|
||
$variant = $this->arguments['variant']; | ||
$variantData = $this->imageDataProvider->getImageVariantData($fileReference, $variant); | ||
|
||
$this->tag->addAttribute('src', $variantData['fallbackImageSrc']); | ||
$this->tag->addAttribute('alt', (string)$fileReference->getAlternative()); | ||
if ($fileReference->getTitle()) { | ||
$this->tag->addAttribute('title', (string)$fileReference->getTitle()); | ||
} | ||
|
||
return $this->tag->render(); | ||
} | ||
} |
Oops, something went wrong.