In my TYPO3 beginnings I always set favicon over typoscript and it’s constants so the editors can place url to the favicon. But sometimes it’s too hard to explain the placing and logic to them (TYPO3 BE is not so funny for beginners). So I said stop, why it shouldn’t be possible to do the same as in WordPress, just one file upload and done? After some play around I found this solution is more practical than I expected.
- No needed to copy+paste things.
- Direct PHP access, so no crazy time of developing in typoscript.
- Possible to unable editors’ access to the Template module.
- Making sure that real square is uploaded and if uploaded image is not square, the editors can choose wanted area.
- Multiple icon settings. In the site head is not only the favicon, but also icon for iphone and others which you can put inside.
Now take a look on my dynamic solution
First we’ll start with extending pages table. We’ll add a new field where would be the file reference stored, so int type is enough.
/* typo3conf/ext/boilerplate/ext_tables.sql */
CREATE TABLE pages (
boilerplate_favicon int(11) DEFAULT 0 NOT NULL
);
Then we would setup a new image crop variant for square (ratio 1:1). Here is an article about cropping variants.
Next step is to extend pages TCA with our new field and it’s crop variant. We can extend it by using a condition to show this TCA only for root pages, but I like more the have possibility to set favicon also for special child pages. The script would check the whole root line and will use the closest filled favicon field.
// typo3conf/ext/boilerplate/Configuration/TCA/Overrides/pages.php
$tempColumns = [
'boilerplate_favicon' => [
'exclude' => true,
'label' => 'LLL:EXT:boilerplate/Resources/Private/Language/locallang_be.xlf:pages.boilerplate_favicon',
'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig(
'boilerplate_favicon',
[
'behaviour' => [
'allowLanguageSynchronization' => true, // Enable language synchronization
],
'appearance' => [
'showPossibleLocalizationRecords' => true,
'showRemovedLocalizationRecords' => true,
'showAllLocalizationLink' => true,
'showSynchronizationLink' => true,
],
'foreign_match_fields' => [
'fieldname' => 'boilerplate_favicon',
'tablenames' => 'pages',
'table_local' => 'sys_file',
],
'overrideChildTca' => [
'types' => [
\TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [
'showitem' => '
--palette--;;imageoverlayPalette,
--palette--;;filePalette',
],
],
'columns' => [
'crop' => [
'config' => [
'cropVariants' => [
'favicon' => [
'disabled' => false // Enabled crop variant with 1:1 ratio
],
'default' => [
'disabled' => true // Disable default crop variant
],
]
]
]
]
],
'maxitems' => 1,
],
'ico,png' // Make possible to upload only ico and png files, cropping is possible only for png.
),
],
];
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTCAcolumns('pages',$tempColumns,1);
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addFieldsToPalette('pages', 'favicon', 'boilerplate_favicon');
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addToAllTCAtypes('pages','
--palette--;;favicon,');
And last step is the integration itself.
I’m using typoscript USER object, where I’m calling custom PHP code. The biggest advantage there is the possibility of generation more then only favicon. You can generate also icons for iphone etc.
# typoscript settings
page.headerData {
21 = USER
21{
userFunc = Vendor\Boilerplate\Service\PageService->generateFavicon
}
}
And here is the PHP source
<?php
declare(strict_types = 1);
namespace Vendor\Boilerplate\Service;
use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Object\ObjectManager;
class PageService
{
public function generateFavicon()
{
$page = $this->getDeepestField(['uid' => $GLOBALS['TSFE']->id], 'boilerplate_favicon', true);
$fileRepository = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Resource\FileRepository::class);
$fileObjects = $fileRepository->findByRelation('pages', 'boilerplate_favicon', $page['uid']);
$imageService = GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Service\ImageService::class);
if ($fileObjects[0]) {
$fileObjects = $fileObjects[0];
// This is for .ico files
if ($fileObjects->getProperty('mime_type') != 'image/png') {
return '<link rel="shortcut icon" href="'.$fileObjects->getOriginalFile()->getPublicUrl().'" type="image/x-icon">';
}
// Crop variant is favicon
$cropString = $fileObjects->getProperty('crop');
$cropVariantCollection = CropVariantCollection::create($cropString);
$cropArea = $cropVariantCollection->getCropArea('favicon');
// Make 32x32px png file
$processingInstructions = [
'crop' => $cropArea->makeAbsoluteBasedOnFile($fileObjects),
'width' => 32,
];
$processedImage = $imageService->applyProcessingInstructions($fileObjects, $processingInstructions);
$imageUri = $imageService->getImageUri($processedImage, true);
$return = '<link rel="shortcut icon" type="image/png" href="'.$imageUri.'">';
// Make 100x100px png file
$processingInstructions = [
'crop' => $cropArea->makeAbsoluteBasedOnFile($fileObjects),
'width' => 100,
];
$processedImage = $imageService->applyProcessingInstructions($fileObjects, $processingInstructions);
$imageUri = $imageService->getImageUri($processedImage, true);
$return .= '<link rel="apple-touch-icon" sizes="180x180" href="'.$imageUri.'">';
return $return;
}
return;
}
protected function getDeepestField($page, $field, $reurnWholePageArray = false)
{
$pageRepository = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Domain\Repository\PageRepository::class);
$page = $pageRepository->getPage($page['uid'], false);
while(empty($page[$field]) && $page['pid'] > 0) {
$page = $pageRepository->getPage($page['pid'], false);
}
if ($reurnWholePageArray) {
return $page;
}
return $page[$field];
}
}