├── Classes ├── Aspect │ ├── FileMetadataOverlayAspect.php │ └── PreviewAspect.php ├── Authentication │ ├── FrontendBackendUserAuthentication.php │ ├── FrontendUserAuthentication.php │ └── ModifyResolvedFrontendGroupsEvent.php ├── Cache │ ├── CacheInstruction.php │ ├── CacheLifetimeCalculator.php │ ├── MetaDataState.php │ └── NonceValueSubstitution.php ├── Category │ └── Collection │ │ └── CategoryCollection.php ├── Content │ ├── ContentSlideMode.php │ └── RecordCollector.php ├── ContentObject │ ├── AbstractContentObject.php │ ├── CaseContentObject.php │ ├── ContentContentObject.php │ ├── ContentDataProcessor.php │ ├── ContentObjectArrayContentObject.php │ ├── ContentObjectArrayInternalContentObject.php │ ├── ContentObjectFactory.php │ ├── ContentObjectGetPublicUrlForFileHookInterface.php │ ├── ContentObjectRenderer.php │ ├── DataProcessorInterface.php │ ├── Event │ │ ├── AfterContentObjectRendererInitializedEvent.php │ │ ├── AfterGetDataResolvedEvent.php │ │ ├── AfterImageResourceResolvedEvent.php │ │ ├── AfterStdWrapFunctionsExecutedEvent.php │ │ ├── AfterStdWrapFunctionsInitializedEvent.php │ │ ├── BeforeStdWrapContentStoredInCacheEvent.php │ │ ├── BeforeStdWrapFunctionsExecutedEvent.php │ │ ├── BeforeStdWrapFunctionsInitializedEvent.php │ │ ├── EnhanceStdWrapEvent.php │ │ ├── ModifyImageSourceCollectionEvent.php │ │ └── ModifyRecordsAfterFetchingContentEvent.php │ ├── Exception │ │ ├── ContentRenderingException.php │ │ ├── ExceptionHandlerInterface.php │ │ └── ProductionExceptionHandler.php │ ├── FileLinkHookInterface.php │ ├── FilesContentObject.php │ ├── FluidTemplateContentObject.php │ ├── HierarchicalMenuContentObject.php │ ├── ImageContentObject.php │ ├── ImageResourceContentObject.php │ ├── LoadRegisterContentObject.php │ ├── Menu │ │ ├── AbstractMenuContentObject.php │ │ ├── CategoryMenuUtility.php │ │ ├── Exception │ │ │ └── NoSuchMenuTypeException.php │ │ ├── MenuContentObjectFactory.php │ │ └── TextMenuContentObject.php │ ├── PageViewContentObject.php │ ├── RecordsContentObject.php │ ├── RestoreRegisterContentObject.php │ ├── ScalableVectorGraphicsContentObject.php │ ├── TextContentObject.php │ ├── UserContentObject.php │ └── UserInternalContentObject.php ├── Controller │ ├── ErrorController.php │ ├── ShowImageController.php │ └── TypoScriptFrontendController.php ├── DataProcessing │ ├── CommaSeparatedValueProcessor.php │ ├── DataProcessorRegistry.php │ ├── DatabaseQueryProcessor.php │ ├── FilesProcessor.php │ ├── FlexFormProcessor.php │ ├── GalleryProcessor.php │ ├── LanguageMenuProcessor.php │ ├── MenuProcessor.php │ ├── PageContentFetchingProcessor.php │ ├── RecordTransformationProcessor.php │ ├── SiteLanguageProcessor.php │ ├── SiteProcessor.php │ └── SplitProcessor.php ├── Event │ ├── AfterCacheableContentIsGeneratedEvent.php │ ├── AfterCachedPageIsPersistedEvent.php │ ├── AfterContentHasBeenFetchedEvent.php │ ├── AfterLinkIsGeneratedEvent.php │ ├── AfterPageAndLanguageIsResolvedEvent.php │ ├── AfterPageWithRootLineIsResolvedEvent.php │ ├── AfterTypoScriptDeterminedEvent.php │ ├── BeforePageCacheIdentifierIsHashedEvent.php │ ├── BeforePageIsResolvedEvent.php │ ├── FilterMenuItemsEvent.php │ ├── ModifyCacheLifetimeForPageEvent.php │ ├── ModifyCacheLifetimeForRowEvent.php │ ├── ModifyHrefLangTagsEvent.php │ ├── ModifyPageLinkConfigurationEvent.php │ ├── ModifyTypoScriptConfigEvent.php │ ├── ModifyTypoScriptConstantsEvent.php │ └── ShouldUseCachedPageDataIfAvailableEvent.php ├── Exception.php ├── Html │ └── HtmlWorker.php ├── Http │ ├── Application.php │ └── RequestHandler.php ├── Imaging │ └── GifBuilder.php ├── Middleware │ ├── BackendUserAuthenticator.php │ ├── CacheTimeout.php │ ├── ContentLengthResponseHeader.php │ ├── ContentSecurityPolicyHeaders.php │ ├── ContentSecurityPolicyReporter.php │ ├── EidHandler.php │ ├── FrontendUserAuthenticator.php │ ├── MaintenanceMode.php │ ├── OutputCompression.php │ ├── PageArgumentValidator.php │ ├── PageResolver.php │ ├── PrepareTypoScriptFrontendRendering.php │ ├── PreviewSimulator.php │ ├── ShortcutAndMountPointRedirect.php │ ├── SiteBaseRedirectResolver.php │ ├── SiteResolver.php │ ├── StaticRouteResolver.php │ ├── TimeTrackerInitialization.php │ └── TypoScriptFrontendInitialization.php ├── Page │ ├── CacheHashCalculator.php │ ├── CacheHashConfiguration.php │ ├── PageAccessFailureReasons.php │ ├── PageInformation.php │ ├── PageInformationCreationFailedException.php │ └── PageInformationFactory.php ├── Resource │ ├── FileCollector.php │ ├── FilePathSanitizer.php │ └── PublicUrlPrefixer.php ├── ServiceProvider.php ├── Typolink │ ├── AbstractTypolinkBuilder.php │ ├── DatabaseRecordLinkBuilder.php │ ├── EmailLinkBuilder.php │ ├── ExternalUrlLinkBuilder.php │ ├── FileOrFolderLinkBuilder.php │ ├── LegacyLinkBuilder.php │ ├── LinkFactory.php │ ├── LinkResult.php │ ├── LinkResultInterface.php │ ├── LinkVarsCalculator.php │ ├── PageLinkBuilder.php │ ├── TelephoneLinkBuilder.php │ └── UnableToLinkException.php └── Utility │ ├── CanonicalizationUtility.php │ └── CompressionUtility.php ├── Configuration ├── ContentSecurityPolicies.php ├── RequestMiddlewares.php ├── Services.php ├── Services.yaml ├── TCA │ ├── Overrides │ │ ├── 100-sys_reaction.php │ │ ├── 200-tt_content.php │ │ ├── 220-tt_content-content_type-textpic.php │ │ ├── 225-tt_content-content_type-image.php │ │ ├── 230-tt_content-content_type-textmedia.php │ │ ├── 235-tt_content-content_type-bullets.php │ │ ├── 240-tt_content-content_type-table.php │ │ ├── 245-tt_content-content_type-uploads.php │ │ ├── 250-tt_content-content_type-menu_abstract.php │ │ ├── 255-tt_content-content_type-menu_categorized_content.php │ │ ├── 255-tt_content-content_type-menu_categorized_pages.php │ │ ├── 260-tt_content-content_type-menu_pages.php │ │ ├── 260-tt_content-content_type-menu_subpages.php │ │ ├── 265-tt_content-content_type-menu_recently_updated.php │ │ ├── 270-tt_content-content_type-menu_related_pages.php │ │ ├── 275-tt_content-content_type-menu_section.php │ │ ├── 275-tt_content-content_type-menu_section_pages.php │ │ ├── 280-tt_content-content_type-menu_sitemap.php │ │ ├── 280-tt_content-content_type-menu_sitemap_pages.php │ │ ├── 285-tt_content-content_type-shortcut.php │ │ ├── 290-tt_content-content_type-div.php │ │ └── 295-tt_content-content_type-html.php │ ├── backend_layout.php │ ├── fe_groups.php │ ├── fe_users.php │ ├── sys_template.php │ └── tt_content.php ├── page.tsconfig └── user.tsconfig ├── LICENSE.txt ├── README.rst ├── Resources ├── Private │ ├── Language │ │ ├── locallang_tca.xlf │ │ └── locallang_ttc.xlf │ └── Templates │ │ └── MainPage.html └── Public │ ├── Icons │ ├── Extension.svg │ └── FileIcons │ │ ├── 3ds.gif │ │ ├── CREDITS.txt │ │ ├── ai.gif │ │ ├── ani.gif │ │ ├── au.gif │ │ ├── avi.gif │ │ ├── bmp.gif │ │ ├── cdr.gif │ │ ├── css.gif │ │ ├── csv.gif │ │ ├── default.gif │ │ ├── doc.gif │ │ ├── dtd.gif │ │ ├── eps.gif │ │ ├── exe.gif │ │ ├── fh3.gif │ │ ├── flash.gif │ │ ├── folder.gif │ │ ├── gif.gif │ │ ├── htm.gif │ │ ├── html.gif │ │ ├── html1.gif │ │ ├── html2.gif │ │ ├── html3.gif │ │ ├── ico.gif │ │ ├── inc.gif │ │ ├── java.gif │ │ ├── jpg.gif │ │ ├── js.gif │ │ ├── max.gif │ │ ├── mid.gif │ │ ├── mov.gif │ │ ├── mp3.gif │ │ ├── mpeg.gif │ │ ├── mpg.gif │ │ ├── pcd.gif │ │ ├── pcx.gif │ │ ├── pdf.gif │ │ ├── php3.gif │ │ ├── png.gif │ │ ├── ppt.gif │ │ ├── ps.gif │ │ ├── psd.gif │ │ ├── rtf.gif │ │ ├── sgml.gif │ │ ├── swf.gif │ │ ├── sxc.gif │ │ ├── sxw.gif │ │ ├── t3d.gif │ │ ├── t3x.gif │ │ ├── tga.gif │ │ ├── tif.gif │ │ ├── tmpl.gif │ │ ├── ttf.gif │ │ ├── txt.gif │ │ ├── wav.gif │ │ ├── wrl.gif │ │ ├── xls.gif │ │ ├── xml.gif │ │ ├── xsl.gif │ │ └── zip.gif │ └── JavaScript │ └── default_frontend.js ├── composer.json ├── ext_emconf.php ├── ext_localconf.php └── ext_tables.sql /Classes/Aspect/FileMetadataOverlayAspect.php: -------------------------------------------------------------------------------- 1 | isFrontend() 46 | || isset($_REQUEST['eID']) 47 | ) { 48 | return; 49 | } 50 | $overlaidMetaData = $event->getRecord(); 51 | $pageRepository = GeneralUtility::makeInstance(PageRepository::class); 52 | $pageRepository->versionOL('sys_file_metadata', $overlaidMetaData); 53 | // getLanguageOverlay also calls versionOL() on the language overlaid record 54 | $overlaidMetaData = $pageRepository->getLanguageOverlay('sys_file_metadata', $overlaidMetaData); 55 | if ($overlaidMetaData !== null) { 56 | $event->setRecord($overlaidMetaData); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Classes/Aspect/PreviewAspect.php: -------------------------------------------------------------------------------- 1 | isPreview = $isPreview; 39 | } 40 | 41 | public function isPreview(): bool 42 | { 43 | return $this->isPreview; 44 | } 45 | 46 | /** 47 | * Get a property from aspect 48 | * 49 | * @return mixed 50 | * @throws AspectPropertyNotFoundException 51 | */ 52 | public function get(string $name) 53 | { 54 | switch ($name) { 55 | case 'isPreview': 56 | return $this->isPreview; 57 | } 58 | throw new AspectPropertyNotFoundException('Property "' . $name . '" not found in Aspect "' . __CLASS__ . '".', 1563375558); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Classes/Authentication/FrontendBackendUserAuthentication.php: -------------------------------------------------------------------------------- 1 | formfield_status is set to empty in order to 48 | * disable login-attempts to the backend account through this script 49 | * 50 | * @var string 51 | * @internal 52 | */ 53 | protected $formfield_status = ''; 54 | 55 | /** 56 | * Decides if the writelog() function is called at login and logout. 57 | * 58 | * @var bool 59 | */ 60 | public $writeStdLog = false; 61 | 62 | /** 63 | * If the writelog() functions is called if a login-attempt has be tried without success. 64 | * 65 | * @var bool 66 | */ 67 | public $writeAttemptLog = false; 68 | 69 | /** 70 | * Implementing the access checks that the TYPO3 CMS bootstrap script does before a user is ever logged in. 71 | * Used in the frontend. 72 | * 73 | * @return bool Returns TRUE if access is OK 74 | */ 75 | public function backendCheckLogin(?ServerRequestInterface $request = null) 76 | { 77 | if (empty($this->user['uid'])) { 78 | return false; 79 | } 80 | // Check Hardcoded lock on BE 81 | if ($GLOBALS['TYPO3_CONF_VARS']['BE']['adminOnly'] < 0) { 82 | return false; 83 | } 84 | return $this->isUserAllowedToLogin(); 85 | } 86 | 87 | /** 88 | * If a user is in a workspace, but previews the live workspace (GET keyword "LIVE") even if the user 89 | * has no editing permissions for this, it should still be visible, even though "be_users.workspace_perms" is set to "0". 90 | * If this ain't true, users without the live permission cannot see the live page, only the preview of the workspace of the user. 91 | */ 92 | protected function hasEditAccessToLiveWorkspace(): bool 93 | { 94 | return true; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Classes/Authentication/ModifyResolvedFrontendGroupsEvent.php: -------------------------------------------------------------------------------- 1 | request; 37 | } 38 | 39 | public function getUser(): FrontendUserAuthentication 40 | { 41 | return $this->user; 42 | } 43 | 44 | public function getGroups(): array 45 | { 46 | return $this->groups; 47 | } 48 | 49 | public function setGroups(array $groups): void 50 | { 51 | $this->groups = $groups; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Classes/Cache/CacheInstruction.php: -------------------------------------------------------------------------------- 1 | allowCaching = false; 56 | $this->disabledCacheReasons[] = $reason; 57 | } 58 | 59 | public function isCachingAllowed(): bool 60 | { 61 | return $this->allowCaching; 62 | } 63 | 64 | /** 65 | * @internal Typically only consumed by extensions like EXT:adminpanel 66 | */ 67 | public function getDisabledCacheReasons(): array 68 | { 69 | return $this->disabledCacheReasons; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Classes/Cache/MetaDataState.php: -------------------------------------------------------------------------------- 1 | json_encode($this->policyRegistry->getMutationCollections()), 41 | ]; 42 | } 43 | 44 | public function updateState(array $state): void 45 | { 46 | foreach ($state as $name => $value) { 47 | switch ($name) { 48 | case 'PolicyRegistry::$mutationCollections': 49 | $this->updatePolicyRegistryMutationCollections($value); 50 | break; 51 | } 52 | } 53 | } 54 | 55 | private function updatePolicyRegistryMutationCollections(mixed $value): void 56 | { 57 | if (!is_string($value) || $value === '') { 58 | return; 59 | } 60 | try { 61 | $array = json_decode($value, true, 512, \JSON_THROW_ON_ERROR); 62 | } catch (\JsonException) { 63 | return; 64 | } 65 | if (!is_array($array)) { 66 | return; 67 | } 68 | $this->policyRegistry->setMutationsCollections( 69 | ...array_map($this->modelService->buildMutationCollectionFromArray(...), $array) 70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Classes/Cache/NonceValueSubstitution.php: -------------------------------------------------------------------------------- 1 | getAttribute('nonce'); 31 | if ($currentNonce === null || empty($context['content']) || empty($context['nonce'])) { 32 | return null; 33 | } 34 | return str_replace($context['nonce'], $currentNonce->consume(), $context['content']); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Classes/Content/ContentSlideMode.php: -------------------------------------------------------------------------------- 1 | self::Slide, 31 | 'collect' => self::Collect, 32 | 'collectReverse' => self::CollectReverse, 33 | default => self::None, 34 | }; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Classes/Content/RecordCollector.php: -------------------------------------------------------------------------------- 1 | getRecords($table, $select); 64 | $recordsOnPid = array_map( 65 | fn(array $record): RecordInterface => $this->recordFactory->createResolvedRecordFromDatabaseRow($table, $record, null, $recordIdentityMap), 66 | $recordsOnPid 67 | ); 68 | 69 | if ($slideCollectReverse) { 70 | $totalRecords = array_merge($totalRecords, $recordsOnPid); 71 | } else { 72 | $totalRecords = array_merge($recordsOnPid, $totalRecords); 73 | } 74 | if ($slide) { 75 | $select['pidInList'] = $contentObjectRenderer->getSlidePids($select['pidInList'] ?? '', $select['pidInList.'] ?? []); 76 | if (isset($select['pidInList.'])) { 77 | unset($select['pidInList.']); 78 | } 79 | $again = $select['pidInList'] !== ''; 80 | } 81 | } while ($again && $slide && ($recordsOnPid === [] || $collect)); 82 | 83 | foreach ($totalRecords as $record) { 84 | $contentObjectRenderer->lastChanged($record); 85 | } 86 | return $totalRecords; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Classes/ContentObject/AbstractContentObject.php: -------------------------------------------------------------------------------- 1 | cObj ?? $this->getTypoScriptFrontendController()->cObj; 54 | } 55 | 56 | public function setRequest(ServerRequestInterface $request): void 57 | { 58 | $this->request = $request; 59 | } 60 | 61 | public function setContentObjectRenderer(ContentObjectRenderer $cObj): void 62 | { 63 | $this->cObj = $cObj; 64 | // Provide the ContentObjectRenderer to the request as well, for code 65 | // that only passes the request to more underlying layers, like Extbase does. 66 | // Also makes sure the request in a Fluid RenderingContext also has the current 67 | // content object available. 68 | $this->request = $this->request->withAttribute('currentContentObject', $cObj); 69 | } 70 | 71 | protected function hasTypoScriptFrontendController(): bool 72 | { 73 | return $this->cObj?->getTypoScriptFrontendController() instanceof TypoScriptFrontendController; 74 | } 75 | 76 | /** 77 | * @throws ContentRenderingException 78 | */ 79 | protected function getTypoScriptFrontendController(): TypoScriptFrontendController 80 | { 81 | if (!$this->hasTypoScriptFrontendController()) { 82 | throw new ContentRenderingException('TypoScriptFrontendController is not available.', 1655723512); 83 | } 84 | 85 | return $this->cObj->getTypoScriptFrontendController(); 86 | } 87 | 88 | protected function getPageRepository(): PageRepository 89 | { 90 | return GeneralUtility::makeInstance(PageRepository::class); 91 | } 92 | 93 | protected function getPageRenderer(): PageRenderer 94 | { 95 | if ($this->pageRenderer === null) { 96 | $this->pageRenderer = GeneralUtility::makeInstance(PageRenderer::class); 97 | } 98 | 99 | return $this->pageRenderer; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Classes/ContentObject/CaseContentObject.php: -------------------------------------------------------------------------------- 1 | cObj->checkIf($conf['if.'])) { 32 | return ''; 33 | } 34 | 35 | $setCurrent = $this->cObj->stdWrapValue('setCurrent', $conf); 36 | if ($setCurrent) { 37 | $this->cObj->data[$this->cObj->currentValKey] = $setCurrent; 38 | } 39 | $key = $this->cObj->stdWrapValue('key', $conf, null); 40 | $key = isset($conf[$key]) && (string)$conf[$key] !== '' ? $key : 'default'; 41 | // If no "default" property is available, then an empty string is returned 42 | if ($key === 'default' && !isset($conf['default'])) { 43 | $theValue = ''; 44 | } else { 45 | $theValue = $this->cObj->cObjGetSingle($conf[$key], $conf[$key . '.'] ?? [], $key); 46 | } 47 | if (isset($conf['stdWrap.'])) { 48 | $theValue = $this->cObj->stdWrap($theValue, $conf['stdWrap.']); 49 | } 50 | return $theValue; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Classes/ContentObject/ContentObjectArrayContentObject.php: -------------------------------------------------------------------------------- 1 | getTimeTracker()->setTSlogMessage('No elements in this content object array (COBJ_ARRAY, COA).', LogLevel::WARNING); 37 | return ''; 38 | } 39 | if (!empty($conf['if.']) && !$this->cObj->checkIf($conf['if.'])) { 40 | return ''; 41 | } 42 | 43 | $content = $this->cObj->cObjGet($conf); 44 | $wrap = $this->cObj->stdWrapValue('wrap', $conf); 45 | if ($wrap) { 46 | $content = $this->cObj->wrap($content, $wrap); 47 | } 48 | if (isset($conf['stdWrap.'])) { 49 | $content = $this->cObj->stdWrap($content, $conf['stdWrap.']); 50 | } 51 | return $content; 52 | } 53 | 54 | /** 55 | * @return TimeTracker 56 | */ 57 | protected function getTimeTracker() 58 | { 59 | return GeneralUtility::makeInstance(TimeTracker::class); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Classes/ContentObject/ContentObjectArrayInternalContentObject.php: -------------------------------------------------------------------------------- 1 | getTimeTracker()->setTSlogMessage('No elements in this content object array (COA_INT).', LogLevel::WARNING); 37 | return ''; 38 | } 39 | 40 | $frontendController = $this->getTypoScriptFrontendController(); 41 | $substKey = 'INT_SCRIPT.' . $frontendController->uniqueHash(); 42 | $content = ''; 43 | $frontendController->config['INTincScript'][$substKey] = [ 44 | 'conf' => $conf, 45 | 'cObj' => serialize($this->cObj), 46 | 'type' => 'COA', 47 | ]; 48 | return $content; 49 | } 50 | 51 | /** 52 | * @return TimeTracker 53 | */ 54 | protected function getTimeTracker() 55 | { 56 | return GeneralUtility::makeInstance(TimeTracker::class); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Classes/ContentObject/ContentObjectFactory.php: -------------------------------------------------------------------------------- 1 | contentObjectLocator->has($name)) { 35 | return null; 36 | } 37 | 38 | $contentObject = $this->contentObjectLocator->get($name); 39 | if (!($contentObject instanceof AbstractContentObject)) { 40 | throw new ContentRenderingException(sprintf('Registered content object class name "%s" must be an instance of AbstractContentObject, but is not!', get_class($contentObject)), 1422564295); 41 | } 42 | 43 | $contentObject->setRequest($request); 44 | $contentObject->setContentObjectRenderer($contentObjectRenderer); 45 | 46 | return $contentObject; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Classes/ContentObject/ContentObjectGetPublicUrlForFileHookInterface.php: -------------------------------------------------------------------------------- 1 | contentObjectRenderer; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Classes/ContentObject/Event/AfterGetDataResolvedEvent.php: -------------------------------------------------------------------------------- 1 | getData() result 24 | */ 25 | final class AfterGetDataResolvedEvent 26 | { 27 | public function __construct( 28 | private readonly string $parameterString, 29 | private readonly array $alternativeFieldArray, 30 | private mixed $result, 31 | private readonly ContentObjectRenderer $contentObjectRenderer 32 | ) {} 33 | 34 | public function getResult(): mixed 35 | { 36 | return $this->result; 37 | } 38 | 39 | public function setResult(mixed $result): void 40 | { 41 | $this->result = $result; 42 | } 43 | 44 | public function getParameterString(): string 45 | { 46 | return $this->parameterString; 47 | } 48 | 49 | public function getAlternativeFieldArray(): array 50 | { 51 | return $this->alternativeFieldArray; 52 | } 53 | 54 | public function getContentObjectRenderer(): ContentObjectRenderer 55 | { 56 | return $this->contentObjectRenderer; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Classes/ContentObject/Event/AfterImageResourceResolvedEvent.php: -------------------------------------------------------------------------------- 1 | getImgResource() result 26 | */ 27 | final class AfterImageResourceResolvedEvent 28 | { 29 | public function __construct( 30 | private readonly string|File|FileReference $file, 31 | private readonly array $fileArray, 32 | private ?ImageResource $imageResource 33 | ) {} 34 | 35 | public function getFile(): string|File|FileReference 36 | { 37 | return $this->file; 38 | } 39 | 40 | public function getFileArray(): array 41 | { 42 | return $this->fileArray; 43 | } 44 | 45 | public function getImageResource(): ?ImageResource 46 | { 47 | return $this->imageResource; 48 | } 49 | 50 | public function setImageResource(?ImageResource $imageResource): void 51 | { 52 | $this->imageResource = $imageResource; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Classes/ContentObject/Event/AfterStdWrapFunctionsExecutedEvent.php: -------------------------------------------------------------------------------- 1 | content; 43 | } 44 | 45 | public function setContent(string $content): void 46 | { 47 | $this->content = $content; 48 | } 49 | 50 | public function getTags(): array 51 | { 52 | return $this->tags; 53 | } 54 | 55 | public function setTags(array $tags): void 56 | { 57 | $this->tags = $tags; 58 | } 59 | 60 | public function getKey(): string 61 | { 62 | return $this->key; 63 | } 64 | 65 | public function setKey(string $key): void 66 | { 67 | $this->key = $key; 68 | } 69 | 70 | public function getLifetime(): ?int 71 | { 72 | return $this->lifetime; 73 | } 74 | 75 | public function setLifetime(?int $lifetime): void 76 | { 77 | $this->lifetime = $lifetime; 78 | } 79 | 80 | public function getConfiguration(): array 81 | { 82 | return $this->configuration; 83 | } 84 | 85 | public function getContentObjectRenderer(): ContentObjectRenderer 86 | { 87 | return $this->contentObjectRenderer; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Classes/ContentObject/Event/BeforeStdWrapFunctionsExecutedEvent.php: -------------------------------------------------------------------------------- 1 | content; 46 | } 47 | 48 | public function setContent(string $content): void 49 | { 50 | $this->content = $content; 51 | } 52 | 53 | public function getConfiguration(): array 54 | { 55 | return $this->configuration; 56 | } 57 | 58 | public function getContentObjectRenderer(): ContentObjectRenderer 59 | { 60 | return $this->contentObjectRenderer; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Classes/ContentObject/Event/ModifyImageSourceCollectionEvent.php: -------------------------------------------------------------------------------- 1 | sourceCollection = $sourceCollection; 38 | } 39 | 40 | public function getSourceCollection(): string 41 | { 42 | return $this->sourceCollection; 43 | } 44 | 45 | public function getFullSourceCollection(): string 46 | { 47 | return $this->fullSourceCollection; 48 | } 49 | 50 | public function getSourceConfiguration(): array 51 | { 52 | return $this->sourceConfiguration; 53 | } 54 | 55 | public function getSourceRenderConfiguration(): array 56 | { 57 | return $this->sourceRenderConfiguration; 58 | } 59 | 60 | public function getContentObjectRenderer(): ContentObjectRenderer 61 | { 62 | return $this->contentObjectRenderer; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Classes/ContentObject/Event/ModifyRecordsAfterFetchingContentEvent.php: -------------------------------------------------------------------------------- 1 | records; 44 | } 45 | 46 | public function setRecords(array $records): void 47 | { 48 | $this->records = $records; 49 | } 50 | 51 | public function getFinalContent(): string 52 | { 53 | return $this->finalContent; 54 | } 55 | 56 | public function setFinalContent(string $finalContent): void 57 | { 58 | $this->finalContent = $finalContent; 59 | } 60 | 61 | public function getSlide(): int 62 | { 63 | return $this->slide; 64 | } 65 | 66 | public function setSlide(int $slide): void 67 | { 68 | $this->slide = $slide; 69 | } 70 | 71 | public function getSlideCollect(): int 72 | { 73 | return $this->slideCollect; 74 | } 75 | 76 | public function setSlideCollect(int $slideCollect): void 77 | { 78 | $this->slideCollect = $slideCollect; 79 | } 80 | 81 | public function getSlideCollectReverse(): bool 82 | { 83 | return $this->slideCollectReverse; 84 | } 85 | 86 | public function setSlideCollectReverse(bool $slideCollectReverse): void 87 | { 88 | $this->slideCollectReverse = $slideCollectReverse; 89 | } 90 | 91 | public function getSlideCollectFuzzy(): bool 92 | { 93 | return $this->slideCollectFuzzy; 94 | } 95 | 96 | public function setSlideCollectFuzzy(bool $slideCollectFuzzy): void 97 | { 98 | $this->slideCollectFuzzy = $slideCollectFuzzy; 99 | } 100 | 101 | public function getConfiguration(): array 102 | { 103 | return $this->configuration; 104 | } 105 | 106 | public function setConfiguration(array $configuration): void 107 | { 108 | $this->configuration = $configuration; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Classes/ContentObject/Exception/ContentRenderingException.php: -------------------------------------------------------------------------------- 1 | configuration = $configuration; 48 | } 49 | 50 | /** 51 | * Handles exceptions thrown during rendering of content objects 52 | * The handler can decide whether to re-throw the exception or 53 | * return a nice error message for production context. 54 | * 55 | * @param AbstractContentObject|null $contentObject 56 | * @param array $contentObjectConfiguration 57 | * @throws \Exception 58 | */ 59 | public function handle(\Exception $exception, ?AbstractContentObject $contentObject = null, $contentObjectConfiguration = []): string 60 | { 61 | // ImmediateResponseException (and the derived PropagateResponseException) should work similar to 62 | // exit / die and must therefore not be handled by this ExceptionHandler. 63 | if ($exception instanceof ImmediateResponseException) { 64 | throw $exception; 65 | } 66 | 67 | if (!empty($this->configuration['ignoreCodes.']) 68 | && in_array($exception->getCode(), array_map('intval', $this->configuration['ignoreCodes.']), true) 69 | ) { 70 | throw $exception; 71 | } 72 | 73 | $errorMessage = $this->configuration['errorMessage'] ?? 'Oops, an error occurred! Request: {requestId}'; 74 | 75 | // $code and it's placeholder %s for b/w compatibility 76 | $code = $this->context->getAspect('date')->getDateTime()->format('YmdHis') . $this->random->generateRandomHexString(8); 77 | $errorMessage = str_replace('%s', '{code}', $errorMessage); 78 | 79 | // Log exception except HMAC validation exceptions caused by potentially forged requests 80 | if (!in_array($exception->getCode(), AbstractExceptionHandler::IGNORED_HMAC_EXCEPTION_CODES, true)) { 81 | $this->logger->alert($errorMessage, ['exception' => $exception, 'code' => $code, 'requestId' => $this->requestId]); 82 | } 83 | 84 | // Return interpolated error message 85 | return str_replace(['{code}', '{requestId}'], [$code, $this->requestId], $errorMessage); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Classes/ContentObject/FileLinkHookInterface.php: -------------------------------------------------------------------------------- 1 | cObj->checkIf($conf['if.'])) { 36 | return ''; 37 | } 38 | 39 | $theValue = ''; 40 | $menuType = $conf[1] ?? ''; 41 | try { 42 | $frontendController = $this->getTypoScriptFrontendController(); 43 | $menuObjectFactory = GeneralUtility::makeInstance(MenuContentObjectFactory::class); 44 | $menu = $menuObjectFactory->getMenuObjectByType($menuType); 45 | if (isset($frontendController->register['count_HMENU'])) { 46 | $frontendController->register['count_HMENU']++; 47 | } else { 48 | $frontendController->register['count_HMENU'] = 1; 49 | } 50 | $frontendController->register['count_HMENU_MENUOBJ'] = 0; 51 | $frontendController->register['count_MENUOBJ'] = 0; 52 | $menu->parent_cObj = $this->getContentObjectRenderer(); 53 | $menu->start(null, $this->getPageRepository(), '', $conf, 1, '', $this->request); 54 | $menu->makeMenu(); 55 | $theValue .= $menu->writeMenu(); 56 | } catch (NoSuchMenuTypeException $e) { 57 | } 58 | $wrap = $this->cObj->stdWrapValue('wrap', $conf); 59 | if ($wrap) { 60 | $theValue = $this->cObj->wrap($theValue, $wrap); 61 | } 62 | if (isset($conf['stdWrap.'])) { 63 | $theValue = $this->cObj->stdWrap($theValue, $conf['stdWrap.']); 64 | } 65 | return $theValue; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Classes/ContentObject/ImageResourceContentObject.php: -------------------------------------------------------------------------------- 1 | cObj->getImgResource($conf['file'] ?? '', $conf['file.'] ?? []); 32 | if ($imageResource === null) { 33 | return ''; 34 | } 35 | return isset($conf['stdWrap.']) 36 | ? $this->cObj->stdWrap($imageResource->getPublicUrl(), $conf['stdWrap.']) 37 | : $imageResource->getPublicUrl(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Classes/ContentObject/LoadRegisterContentObject.php: -------------------------------------------------------------------------------- 1 | getTypoScriptFrontendController(); 33 | $frontendController->registerStack[] = $frontendController->register; 34 | if (is_array($conf)) { 35 | $isExecuted = []; 36 | foreach ($conf as $theKey => $theValue) { 37 | $register = rtrim($theKey, '.'); 38 | if (!isset($isExecuted[$register]) || !$isExecuted[$register]) { 39 | $registerProperties = $register . '.'; 40 | if (isset($conf[$register]) && isset($conf[$registerProperties])) { 41 | $theValue = $this->cObj->stdWrap($conf[$register], $conf[$registerProperties]); 42 | } elseif (isset($conf[$registerProperties])) { 43 | $theValue = $this->cObj->stdWrap('', $conf[$registerProperties]); 44 | } 45 | $frontendController->register[$register] = $theValue; 46 | $isExecuted[$register] = true; 47 | } 48 | } 49 | } 50 | return ''; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Classes/ContentObject/Menu/Exception/NoSuchMenuTypeException.php: -------------------------------------------------------------------------------- 1 | TextMenuContentObject::class, 37 | ]; 38 | 39 | /** 40 | * Gets a typo script string like 'TMENU' and returns an object of this type 41 | * 42 | * @throws Exception\NoSuchMenuTypeException 43 | */ 44 | public function getMenuObjectByType(string $type = ''): AbstractMenuContentObject 45 | { 46 | $upperCasedClassName = strtoupper($type); 47 | if (array_key_exists($upperCasedClassName, $this->menuTypeToClassMapping)) { 48 | /** @var AbstractMenuContentObject $object */ 49 | $object = GeneralUtility::makeInstance($this->menuTypeToClassMapping[$upperCasedClassName]); 50 | return $object; 51 | } 52 | throw new NoSuchMenuTypeException( 53 | 'Menu type ' . (string)$type . ' has no implementing class.', 54 | 1363278130 55 | ); 56 | } 57 | 58 | /** 59 | * Register new menu type or override existing type 60 | * 61 | * @param string $type Menu type to be used in TypoScript 62 | * @param string $className Class rendering the menu 63 | */ 64 | public function registerMenuType(string $type, string $className) 65 | { 66 | $this->menuTypeToClassMapping[strtoupper($type)] = $className; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Classes/ContentObject/RestoreRegisterContentObject.php: -------------------------------------------------------------------------------- 1 | getTypoScriptFrontendController(); 33 | $frontendController->register = array_pop($frontendController->registerStack) ?? []; 34 | return ''; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Classes/ContentObject/TextContentObject.php: -------------------------------------------------------------------------------- 1 | cObj->stdWrap($content, $conf['value.']); 41 | unset($conf['value.']); 42 | } 43 | if (!empty($conf)) { 44 | $content = $this->cObj->stdWrap($content, $conf); 45 | } 46 | return $content; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Classes/ContentObject/UserContentObject.php: -------------------------------------------------------------------------------- 1 | getTimeTracker()->setTSlogMessage('USER without configuration.', LogLevel::WARNING); 37 | return ''; 38 | } 39 | $content = ''; 40 | if ($this->cObj->getUserObjectType() === false) { 41 | // Come here only if we are not called from $TSFE->processNonCacheableContentPartsAndSubstituteContentMarkers()! 42 | $this->cObj->setUserObjectType(ContentObjectRenderer::OBJECTTYPE_USER); 43 | } 44 | $tempContent = $this->cObj->callUserFunction($conf['userFunc'] ?? '', $conf, ''); 45 | if ($this->cObj->doConvertToUserIntObject) { 46 | $this->cObj->doConvertToUserIntObject = false; 47 | $content = $this->cObj->cObjGetSingle('USER_INT', $conf); 48 | } else { 49 | $content .= $tempContent; 50 | // Only executed when the element is not converted to USER_INT 51 | if (isset($conf['stdWrap.'])) { 52 | $content = $this->cObj->stdWrap($content, $conf['stdWrap.']); 53 | } 54 | } 55 | $this->cObj->setUserObjectType(false); 56 | return $content; 57 | } 58 | 59 | /** 60 | * @return TimeTracker 61 | */ 62 | protected function getTimeTracker() 63 | { 64 | return GeneralUtility::makeInstance(TimeTracker::class); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Classes/ContentObject/UserInternalContentObject.php: -------------------------------------------------------------------------------- 1 | cObj->setUserObjectType(ContentObjectRenderer::OBJECTTYPE_USER_INT); 32 | $tsfe = $this->getTypoScriptFrontendController(); 33 | $substKey = 'INT_SCRIPT.' . $tsfe->uniqueHash(); 34 | $content = ''; 35 | $tsfe->config['INTincScript'][$substKey] = [ 36 | 'conf' => $conf, 37 | 'cObj' => serialize($this->cObj), 38 | 'type' => 'FUNC', 39 | ]; 40 | $this->cObj->setUserObjectType(false); 41 | return $content; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Classes/DataProcessing/DataProcessorRegistry.php: -------------------------------------------------------------------------------- 1 | dataProcessorLocator->has($identifer)) { 34 | return null; 35 | } 36 | 37 | $dataProcessor = $this->dataProcessorLocator->get($identifer); 38 | if (!($dataProcessor instanceof DataProcessorInterface)) { 39 | throw new \UnexpectedValueException( 40 | 'Processor with alias / identifier "' . $identifer . '" ' . 41 | 'must implement interface "' . DataProcessorInterface::class . '"', 42 | 1666131903 43 | ); 44 | } 45 | 46 | return $dataProcessor; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Classes/DataProcessing/SiteLanguageProcessor.php: -------------------------------------------------------------------------------- 1 | stdWrapValue('as', $processorConfiguration, 'siteLanguage'); 47 | $processedData[$targetVariableName] = $cObj->getRequest()->getAttribute('language')?->toArray(); 48 | return $processedData; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Classes/DataProcessing/SiteProcessor.php: -------------------------------------------------------------------------------- 1 | stdWrapValue('as', $processorConfiguration, 'site'); 47 | $processedData[$targetVariableName] = $cObj->getRequest()->getAttribute('site'); 48 | return $processedData; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Classes/Event/AfterCacheableContentIsGeneratedEvent.php: -------------------------------------------------------------------------------- 1 | isCachingEnabled() as the same as $TSFE->no_cache. 26 | * Depending on disable or enabling caching, the cache is then not stored in the pageCache. 27 | */ 28 | final class AfterCacheableContentIsGeneratedEvent 29 | { 30 | public function __construct( 31 | private readonly ServerRequestInterface $request, 32 | private readonly TypoScriptFrontendController $controller, 33 | private readonly string $cacheIdentifier, 34 | private bool $usePageCache 35 | ) {} 36 | 37 | public function getRequest(): ServerRequestInterface 38 | { 39 | return $this->request; 40 | } 41 | 42 | public function getController(): TypoScriptFrontendController 43 | { 44 | return $this->controller; 45 | } 46 | 47 | public function isCachingEnabled(): bool 48 | { 49 | return $this->usePageCache; 50 | } 51 | 52 | public function disableCaching(): void 53 | { 54 | $this->usePageCache = false; 55 | } 56 | 57 | public function enableCaching(): void 58 | { 59 | $this->usePageCache = true; 60 | } 61 | 62 | public function getCacheIdentifier(): string 63 | { 64 | return $this->cacheIdentifier; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Classes/Event/AfterCachedPageIsPersistedEvent.php: -------------------------------------------------------------------------------- 1 | request; 44 | } 45 | 46 | public function getController(): TypoScriptFrontendController 47 | { 48 | return $this->controller; 49 | } 50 | 51 | public function getCacheIdentifier(): string 52 | { 53 | return $this->cacheIdentifier; 54 | } 55 | 56 | public function getCacheData(): array 57 | { 58 | return $this->cacheData; 59 | } 60 | 61 | /** 62 | * The amount of seconds until the cache entry is invalid. 63 | */ 64 | public function getCacheLifetime(): int 65 | { 66 | return $this->cacheLifetime; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Classes/Event/AfterContentHasBeenFetchedEvent.php: -------------------------------------------------------------------------------- 1 | linkResult = $linkResult; 43 | } 44 | 45 | public function getLinkResult(): LinkResultInterface 46 | { 47 | return $this->linkResult; 48 | } 49 | 50 | public function getContentObjectRenderer(): ContentObjectRenderer 51 | { 52 | return $this->contentObjectRenderer; 53 | } 54 | 55 | /** 56 | * Returns the original instructions / $linkConfiguration that were used to build the link 57 | */ 58 | public function getLinkInstructions(): array 59 | { 60 | return $this->linkInstructions; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Classes/Event/AfterPageAndLanguageIsResolvedEvent.php: -------------------------------------------------------------------------------- 1 | request; 43 | } 44 | 45 | public function getPageInformation(): PageInformation 46 | { 47 | return $this->pageInformation; 48 | } 49 | 50 | public function setPageInformation(PageInformation $pageInformation): void 51 | { 52 | $this->pageInformation = $pageInformation; 53 | } 54 | 55 | public function getResponse(): ?ResponseInterface 56 | { 57 | return $this->response; 58 | } 59 | 60 | public function setResponse(ResponseInterface $response): void 61 | { 62 | $this->response = $response; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Classes/Event/AfterPageWithRootLineIsResolvedEvent.php: -------------------------------------------------------------------------------- 1 | request; 42 | } 43 | 44 | public function setResponse(ResponseInterface $response): void 45 | { 46 | $this->response = $response; 47 | } 48 | 49 | public function getResponse(): ?ResponseInterface 50 | { 51 | return $this->response; 52 | } 53 | 54 | public function getPageInformation(): PageInformation 55 | { 56 | return $this->pageInformation; 57 | } 58 | 59 | public function setPageInformation(PageInformation $pageInformation): void 60 | { 61 | $this->pageInformation = $pageInformation; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Classes/Event/AfterTypoScriptDeterminedEvent.php: -------------------------------------------------------------------------------- 1 | frontendTypoScript; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Classes/Event/BeforePageCacheIdentifierIsHashedEvent.php: -------------------------------------------------------------------------------- 1 | request; 46 | } 47 | 48 | public function getPageCacheIdentifierParameters(): array 49 | { 50 | return $this->pageCacheIdentifierParameters; 51 | } 52 | 53 | public function setPageCacheIdentifierParameters(array $pageCacheIdentifierParameters): void 54 | { 55 | $this->pageCacheIdentifierParameters = $pageCacheIdentifierParameters; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Classes/Event/BeforePageIsResolvedEvent.php: -------------------------------------------------------------------------------- 1 | id) or modify the context 28 | * for resolving a page. 29 | */ 30 | final class BeforePageIsResolvedEvent 31 | { 32 | public function __construct( 33 | private readonly ServerRequestInterface $request, 34 | private PageInformation $pageInformation, 35 | ) {} 36 | 37 | public function getRequest(): ServerRequestInterface 38 | { 39 | return $this->request; 40 | } 41 | 42 | public function getPageInformation(): PageInformation 43 | { 44 | return $this->pageInformation; 45 | } 46 | 47 | public function setPageInformation(PageInformation $pageInformation): void 48 | { 49 | $this->pageInformation = $pageInformation; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Classes/Event/FilterMenuItemsEvent.php: -------------------------------------------------------------------------------- 1 | allMenuItems; 43 | } 44 | 45 | public function getFilteredMenuItems(): array 46 | { 47 | return $this->filteredMenuItems; 48 | } 49 | 50 | public function setFilteredMenuItems(array $filteredMenuItems): void 51 | { 52 | $this->filteredMenuItems = $filteredMenuItems; 53 | } 54 | 55 | public function getMenuConfiguration(): array 56 | { 57 | return $this->menuConfiguration; 58 | } 59 | 60 | public function getItemConfiguration(): array 61 | { 62 | return $this->itemConfiguration; 63 | } 64 | 65 | public function getBannedMenuItems(): array 66 | { 67 | return $this->bannedMenuItems; 68 | } 69 | 70 | public function getExcludedDoktypes(): array 71 | { 72 | return $this->excludedDoktypes; 73 | } 74 | 75 | public function getSite(): Site 76 | { 77 | return $this->site; 78 | } 79 | 80 | public function getContext(): Context 81 | { 82 | return $this->context; 83 | } 84 | 85 | public function getCurrentPage(): array 86 | { 87 | return $this->currentPage; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Classes/Event/ModifyCacheLifetimeForPageEvent.php: -------------------------------------------------------------------------------- 1 | cacheLifetime = $cacheLifetime; 39 | } 40 | 41 | public function getCacheLifetime(): int 42 | { 43 | return $this->cacheLifetime; 44 | } 45 | 46 | public function getPageId(): int 47 | { 48 | return $this->pageId; 49 | } 50 | 51 | public function getPageRecord(): array 52 | { 53 | return $this->pageRecord; 54 | } 55 | 56 | public function getRenderingInstructions(): array 57 | { 58 | return $this->renderingInstructions; 59 | } 60 | 61 | public function getContext(): Context 62 | { 63 | return $this->context; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Classes/Event/ModifyCacheLifetimeForRowEvent.php: -------------------------------------------------------------------------------- 1 | hrefLangs; 35 | } 36 | 37 | public function getRequest(): ServerRequestInterface 38 | { 39 | return $this->request; 40 | } 41 | 42 | /** 43 | * Set the hreflangs. This should be an array in format: 44 | * 45 | * ``` 46 | * [ 47 | * 'en-US' => 'https://example.com', 48 | * 'nl-NL' => 'https://example.com/nl' 49 | * ] 50 | * ``` 51 | * 52 | * @param array $hrefLangs 53 | */ 54 | public function setHrefLangs(array $hrefLangs): void 55 | { 56 | $this->hrefLangs = $hrefLangs; 57 | } 58 | 59 | /** 60 | * Add a hreflang tag to the current list of hreflang tags 61 | * 62 | * @param string $languageCode The language of the hreflang tag you would like to add. For example: nl-NL 63 | * @param string $url The URL of the translation. For example: https://example.com/nl 64 | */ 65 | public function addHrefLang(string $languageCode, string $url): void 66 | { 67 | $this->hrefLangs[$languageCode] = $url; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Classes/Event/ModifyPageLinkConfigurationEvent.php: -------------------------------------------------------------------------------- 1 | configuration; 41 | } 42 | 43 | public function setConfiguration(array $configuration): void 44 | { 45 | $this->configuration = $configuration; 46 | } 47 | 48 | public function getLinkDetails(): array 49 | { 50 | return $this->linkDetails; 51 | } 52 | 53 | public function getPage(): array 54 | { 55 | return $this->page; 56 | } 57 | 58 | public function setPage(array $page): void 59 | { 60 | $this->page = $page; 61 | $this->pageWasModified = true; 62 | } 63 | 64 | public function getQueryParameters(): array 65 | { 66 | return $this->queryParameters; 67 | } 68 | 69 | public function setQueryParameters(array $queryParameters): void 70 | { 71 | $this->queryParameters = $queryParameters; 72 | } 73 | 74 | public function getFragment(): string 75 | { 76 | return $this->fragment; 77 | } 78 | 79 | public function setFragment(string $fragment): void 80 | { 81 | $this->fragment = $fragment; 82 | } 83 | 84 | public function pageWasModified(): bool 85 | { 86 | return $this->pageWasModified; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Classes/Event/ModifyTypoScriptConfigEvent.php: -------------------------------------------------------------------------------- 1 | getAttribute('frontend.typoscript')->getConfigTree(), 36 | * and its array variant $request->getAttribute('frontend.typoscript')->getConfigArray(). 37 | * 38 | * Registered listener can *set* a modified setup config AST. Note the TypoScript AST 39 | * structure is still marked @internal within v13 core and may change later, 40 | * using the event to *write* different 'config' data is thus still a bit risky. 41 | */ 42 | final class ModifyTypoScriptConfigEvent 43 | { 44 | public function __construct( 45 | private readonly ServerRequestInterface $request, 46 | private readonly RootNode $setupTree, 47 | private RootNode $configTree, 48 | ) {} 49 | 50 | public function getRequest(): ServerRequestInterface 51 | { 52 | return $this->request; 53 | } 54 | 55 | public function getSetupTree(): RootNode 56 | { 57 | return $this->setupTree; 58 | } 59 | 60 | public function getConfigTree(): RootNode 61 | { 62 | return $this->configTree; 63 | } 64 | 65 | public function setConfigTree(RootNode $configTree): void 66 | { 67 | $this->configTree = $configTree; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Classes/Event/ModifyTypoScriptConstantsEvent.php: -------------------------------------------------------------------------------- 1 | constantsAst; 37 | } 38 | 39 | public function setConstantsAst(RootNode $constantsAst): void 40 | { 41 | $this->constantsAst = $constantsAst; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Classes/Event/ShouldUseCachedPageDataIfAvailableEvent.php: -------------------------------------------------------------------------------- 1 | request->getAttribute('frontend.controller'); 40 | } 41 | 42 | public function getRequest(): ServerRequestInterface 43 | { 44 | return $this->request; 45 | } 46 | 47 | public function shouldUseCachedPageData(): bool 48 | { 49 | return $this->shouldUseCachedPageData; 50 | } 51 | 52 | public function setShouldUseCachedPageData(bool $shouldUseCachedPageData): void 53 | { 54 | $this->shouldUseCachedPageData = $shouldUseCachedPageData; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Classes/Exception.php: -------------------------------------------------------------------------------- 1 | requestHandler = $requestHandler; 42 | } 43 | 44 | public function handle(ServerRequestInterface $request): ResponseInterface 45 | { 46 | // Create new request object having applicationType "I am a frontend request" attribute. 47 | $request = $request->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_FE); 48 | 49 | $this->initializeContext(); 50 | return parent::handle($request); 51 | } 52 | 53 | /** 54 | * Initializes the Context used for accessing data and finding out the current state of the application 55 | */ 56 | protected function initializeContext(): void 57 | { 58 | $this->context->setAspect( 59 | 'date', 60 | new DateTimeAspect( 61 | DateTimeFactory::createFromTimestamp($GLOBALS['EXEC_TIME']) 62 | ) 63 | ); 64 | $this->context->setAspect('visibility', new VisibilityAspect()); 65 | $this->context->setAspect('workspace', new WorkspaceAspect(0)); 66 | $this->context->setAspect('backend.user', new UserAspect(null)); 67 | $this->context->setAspect('frontend.user', new UserAspect(null, [0, -1])); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Classes/Middleware/CacheTimeout.php: -------------------------------------------------------------------------------- 1 | handle($request); 35 | $config = $request->getAttribute('frontend.controller')?->config['config'] ?? []; 36 | if ($config['cache_clearAtMidnight'] ?? false) { 37 | // @todo: We should probably decide to deprecate or remove cache_clearAtMidnight 38 | // altogether since it is a flawed concept based on server timezone 39 | // "when is midnight?". 40 | $cacheDataCollector = $request->getAttribute('frontend.cache.collector'); 41 | $timeOutTime = min($GLOBALS['EXEC_TIME'] + $cacheDataCollector->resolveLifetime(), PHP_INT_MAX); 42 | $midnightTime = mktime(0, 0, 0, (int)date('m', $timeOutTime), (int)date('d', $timeOutTime), (int)date('Y', $timeOutTime)); 43 | // If the midnight time of the expire-day is greater than the current time, 44 | // we may set the timeOutTime to the new midnighttime. 45 | if ($midnightTime > $GLOBALS['EXEC_TIME']) { 46 | $cacheDataCollector->restrictMaximumLifetime($midnightTime - $GLOBALS['EXEC_TIME']); 47 | } 48 | } 49 | return $response; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Classes/Middleware/ContentLengthResponseHeader.php: -------------------------------------------------------------------------------- 1 | handle($request); 42 | $typoScriptConfigArray = $request->getAttribute('frontend.typoscript')->getConfigArray(); 43 | if ( 44 | (!isset($typoScriptConfigArray['enableContentLengthHeader']) || $typoScriptConfigArray['enableContentLengthHeader']) 45 | && !$this->context->getPropertyFromAspect('backend.user', 'isLoggedIn', false) 46 | && !$this->context->getPropertyFromAspect('workspace', 'isOffline', false) 47 | ) { 48 | $response = $response->withHeader('Content-Length', (string)$response->getBody()->getSize()); 49 | } 50 | return $response; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Classes/Middleware/ContentSecurityPolicyReporter.php: -------------------------------------------------------------------------------- 1 | getAttribute('site'); 37 | $scope = Scope::frontendSite($site); 38 | if ($this->targetsCspReportUri($scope, $request)) { 39 | $dispositionMap = $this->dispositionMapFactory->buildDispositionMap( 40 | $site instanceof Site ? ($site->getConfiguration()['contentSecurityPolicies'] ?? []) : [] 41 | ); 42 | // find at least one configured reporting endpoint for the current request 43 | foreach ($dispositionMap->values() as $dispositionConfiguration) { 44 | if ($this->isCspReport($scope, $request, $dispositionConfiguration)) { 45 | $isCspReport = true; 46 | break; 47 | } 48 | } 49 | if (!($isCspReport ?? false)) { 50 | return new HtmlResponse('Submission to CSP reporting endpoint denied', 403); 51 | } 52 | // @todo check/store headers `origin` + `referer` 53 | // @todo create report, then call persist, then dispatch new event 54 | $this->persistCspReport($scope, $request); 55 | return (new Response())->withStatus(201); 56 | } 57 | return $handler->handle($request); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Classes/Middleware/EidHandler.php: -------------------------------------------------------------------------------- 1 | getParsedBody()['eID'] ?? $request->getQueryParams()['eID'] ?? null; 46 | 47 | if ($eID === null) { 48 | return $handler->handle($request); 49 | } 50 | 51 | // Remove any output produced until now 52 | ob_clean(); 53 | 54 | if (!is_string($eID)) { 55 | return (new Response())->withStatus(400, 'Invalid eID'); 56 | } 57 | 58 | $target = $GLOBALS['TYPO3_CONF_VARS']['FE']['eID_include'][$eID] ?? null; 59 | if (empty($target)) { 60 | return (new Response())->withStatus(404, 'eID not registered'); 61 | } 62 | 63 | $request = $request->withAttribute('target', $target); 64 | return $this->dispatcher->dispatch($request); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Classes/Middleware/MaintenanceMode.php: -------------------------------------------------------------------------------- 1 | getAttribute('normalizedParams')->getRemoteAddress(), 45 | $GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask'] 46 | ) 47 | ) { 48 | return GeneralUtility::makeInstance(ErrorController::class)->unavailableAction($request, 'This page is temporarily unavailable.'); 49 | } 50 | // Continue the regular stack if no maintenance mode is active 51 | return $handler->handle($request); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Classes/Middleware/OutputCompression.php: -------------------------------------------------------------------------------- 1 | initializeOutputCompression(); 44 | return $handler->handle($request); 45 | } 46 | 47 | /** 48 | * Initialize output compression if configured 49 | */ 50 | protected function initializeOutputCompression(): void 51 | { 52 | if ($GLOBALS['TYPO3_CONF_VARS']['FE']['compressionLevel'] && extension_loaded('zlib')) { 53 | if (MathUtility::canBeInterpretedAsInteger($GLOBALS['TYPO3_CONF_VARS']['FE']['compressionLevel'])) { 54 | @ini_set('zlib.output_compression_level', (string)$GLOBALS['TYPO3_CONF_VARS']['FE']['compressionLevel']); 55 | } 56 | ob_start([GeneralUtility::makeInstance(CompressionUtility::class), 'compressionOutputHandler']); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Classes/Middleware/SiteResolver.php: -------------------------------------------------------------------------------- 1 | matcher->matchRequest($request); 55 | 56 | $site = $routeResult->getSite(); 57 | if ($site instanceof Site && $site->invalidSets !== []) { 58 | $invalidSets = implode(', ', array_keys($site->invalidSets)); 59 | $this->logger->error('Site {identifier} depends on unavailable sets: {invalidSets}', [ 60 | 'identifier' => $site->getIdentifier(), 61 | 'invalidSets' => $invalidSets, 62 | ]); 63 | return $this->errorController->internalErrorAction( 64 | $request, 65 | sprintf( 66 | 'Site %s depends on unavailable sets: %s', 67 | $site->getIdentifier(), 68 | $invalidSets, 69 | ), 70 | ['code' => PageAccessFailureReasons::INVALID_SITE_SETS] 71 | ); 72 | } 73 | 74 | $request = $request->withAttribute('site', $site); 75 | $request = $request->withAttribute('language', $routeResult->getLanguage()); 76 | $request = $request->withAttribute('routing', $routeResult); 77 | if ($routeResult->getLanguage() instanceof SiteLanguage) { 78 | Locales::setSystemLocaleFromSiteLanguage($routeResult->getLanguage()); 79 | } 80 | return $handler->handle($request); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Classes/Middleware/TimeTrackerInitialization.php: -------------------------------------------------------------------------------- 1 | isBackendUserCookieSet($request); 45 | $this->timeTracker->setEnabled($timeTrackingEnabled); 46 | $this->timeTracker->start(microtime(true)); 47 | $this->timeTracker->push(''); 48 | 49 | $response = $handler->handle($request); 50 | 51 | // Finish time tracking 52 | $this->timeTracker->pull(); 53 | $this->timeTracker->finish(); 54 | 55 | if ($this->isDebugModeEnabled()) { 56 | return $response->withHeader('X-TYPO3-Parsetime', $this->timeTracker->getParseTime() . 'ms'); 57 | } 58 | return $response; 59 | } 60 | 61 | /** 62 | * This middleware is run pretty early in the FE chain to initialize correctly. 63 | * It however should only add the response header if debugging is enabled in TypoScript 'config', 64 | * which is not available in the incoming Request, yet. 65 | * It thus listens on the AfterTypoScriptDeterminedEvent to set $this->isDebugEnabledInTypoScriptConfig. 66 | */ 67 | #[AsEventListener('typo3-frontend/timetracker-init-middleware')] 68 | public function typoScriptDeterminedListener(AfterTypoScriptDeterminedEvent $event): void 69 | { 70 | $typoScriptConfig = $event->getFrontendTypoScript()->getConfigArray(); 71 | if (!empty($typoScriptConfig['debug'] ?? false)) { 72 | $this->isDebugEnabledInTypoScriptConfig = true; 73 | } 74 | } 75 | 76 | protected function isBackendUserCookieSet(ServerRequestInterface $request): bool 77 | { 78 | $configuredCookieName = trim($GLOBALS['TYPO3_CONF_VARS']['BE']['cookieName']) ?: 'be_typo_user'; 79 | return !empty($request->getCookieParams()[$configuredCookieName]); 80 | } 81 | 82 | protected function isDebugModeEnabled(): bool 83 | { 84 | if ($this->isDebugEnabledInTypoScriptConfig) { 85 | return true; 86 | } 87 | return !empty($GLOBALS['TYPO3_CONF_VARS']['FE']['debug']); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Classes/Page/PageInformationCreationFailedException.php: -------------------------------------------------------------------------------- 1 | code = $code; 37 | } 38 | 39 | public function getResponse(): ResponseInterface 40 | { 41 | return $this->response; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Classes/Resource/PublicUrlPrefixer.php: -------------------------------------------------------------------------------- 1 | getCurrentFrontendController(); 39 | if (self::$isProcessingUrl || !$controller) { 40 | return; 41 | } 42 | $resource = $event->getResource(); 43 | if (!$this->isLocalResource($resource)) { 44 | return; 45 | } 46 | 47 | // Before calling getPublicUrl, we set the static property to true to avoid to be called in a loop 48 | self::$isProcessingUrl = true; 49 | try { 50 | $resource = $event->getResource(); 51 | $originalUrl = $event->getStorage()->getPublicUrl($resource); 52 | if (!$originalUrl || PathUtility::hasProtocolAndScheme($originalUrl)) { 53 | return; 54 | } 55 | $event->setPublicUrl($controller->absRefPrefix . $originalUrl); 56 | } finally { 57 | self::$isProcessingUrl = false; 58 | } 59 | } 60 | 61 | private function isLocalResource(ResourceInterface $resource): bool 62 | { 63 | return $resource->getStorage()->getDriverType() === 'Local'; 64 | } 65 | 66 | private function getCurrentFrontendController(): ?TypoScriptFrontendController 67 | { 68 | return $GLOBALS['TSFE'] ?? null; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Classes/ServiceProvider.php: -------------------------------------------------------------------------------- 1 | self::getApplication(...), 50 | 'frontend.middlewares' => self::getFrontendMiddlewares(...), 51 | ]; 52 | } 53 | 54 | public function getExtensions(): array 55 | { 56 | return [ 57 | Http\RequestHandler::class => self::provideFallbackRequestHandler(...), 58 | ] + parent::getExtensions(); 59 | } 60 | 61 | public static function getApplication(ContainerInterface $container): Http\Application 62 | { 63 | $requestHandler = new MiddlewareDispatcher( 64 | $container->get(Http\RequestHandler::class), 65 | $container->get('frontend.middlewares'), 66 | $container 67 | ); 68 | 69 | return self::new($container, Http\Application::class, [ 70 | $requestHandler, 71 | $container->get(Context::class), 72 | ]); 73 | } 74 | 75 | public static function provideFallbackRequestHandler( 76 | ContainerInterface $container, 77 | ?RequestHandlerInterface $requestHandler = null 78 | ): RequestHandlerInterface { 79 | // Provide fallback request handler instace for the case where the system is not installed yet (that means when we run without symfony DI). 80 | // This request handler is intended to be never executed, as the frontend application will perform an early redirect to the install tool. 81 | return $requestHandler ?? new class () implements RequestHandlerInterface { 82 | public function handle(ServerRequestInterface $request): ResponseInterface 83 | { 84 | throw new \RuntimeException('not implemented', 1689684150); 85 | } 86 | }; 87 | } 88 | 89 | /** 90 | * @throws InvalidDataException 91 | * @throws CoreException 92 | */ 93 | public static function getFrontendMiddlewares(ContainerInterface $container): \ArrayObject 94 | { 95 | return new \ArrayObject($container->get(MiddlewareStackResolver::class)->resolve('frontend')); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Classes/Typolink/ExternalUrlLinkBuilder.php: -------------------------------------------------------------------------------- 1 | forceAbsoluteUrl($url, $conf); 34 | $fallbackTarget = str_starts_with($url, '/') && !str_starts_with($url, '//') ? 'target' : 'extTarget'; 35 | 36 | $linkText = $this->encodeFallbackLinkTextIfLinkTextIsEmpty($linkText, $url); 37 | return (new LinkResult(LinkService::TYPE_URL, (string)$url)) 38 | ->withLinkConfiguration($conf) 39 | ->withTarget( 40 | $target ?: $this->resolveTargetAttribute($conf, $fallbackTarget), 41 | ) 42 | ->withLinkText($linkText); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Classes/Typolink/FileOrFolderLinkBuilder.php: -------------------------------------------------------------------------------- 1 | getPublicUrl(); 41 | if ($linkLocation === null) { 42 | // set the linkLocation to an empty string if null, 43 | // so it does not collide with the various string functions 44 | $linkLocation = ''; 45 | } 46 | // Setting title if blank value to link 47 | $linkText = $this->encodeFallbackLinkTextIfLinkTextIsEmpty($linkText, rawurldecode($linkLocation)); 48 | $url = $linkLocation; 49 | if (!empty($linkDetails['fragment'])) { 50 | $url .= '#' . $linkDetails['fragment']; 51 | } 52 | return (new LinkResult($linkDetails['type'], $this->forceAbsoluteUrl($url, $conf))) 53 | ->withLinkConfiguration($conf) 54 | ->withTarget($target ?: $this->resolveTargetAttribute($conf, 'fileTarget')) 55 | ->withLinkText($linkText); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Classes/Typolink/LegacyLinkBuilder.php: -------------------------------------------------------------------------------- 1 | getTypoScriptFrontendController(); 31 | if ($linkDetails['file'] ?? false) { 32 | $linkDetails['type'] = LinkService::TYPE_FILE; 33 | $linkLocation = $linkDetails['file']; 34 | // Setting title if blank value to link 35 | $linkText = $this->encodeFallbackLinkTextIfLinkTextIsEmpty($linkText, rawurldecode($linkLocation)); 36 | $linkLocation = (!str_starts_with($linkLocation, '/') ? $tsfe->absRefPrefix : '') . $linkLocation; 37 | $url = $linkLocation; 38 | $url = $this->forceAbsoluteUrl($url, $conf); 39 | $target = $target ?: $this->resolveTargetAttribute($conf, 'fileTarget'); 40 | } elseif ($linkDetails['url'] ?? false) { 41 | $linkDetails['type'] = LinkService::TYPE_URL; 42 | $target = $target ?: $this->resolveTargetAttribute($conf, 'extTarget'); 43 | $linkText = $this->encodeFallbackLinkTextIfLinkTextIsEmpty($linkText, $linkDetails['url']); 44 | $url = $linkDetails['url']; 45 | } else { 46 | throw new UnableToLinkException('Unknown link detected, so ' . $linkText . ' was not linked.', 1490990031, null, $linkText); 47 | } 48 | return (new LinkResult((string)$linkDetails['type'], (string)$url))->withTarget($target)->withLinkConfiguration($conf)->withLinkText($linkText); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Classes/Typolink/LinkResultInterface.php: -------------------------------------------------------------------------------- 1 | withLinkConfiguration($conf)->withLinkText($linkText); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Classes/Typolink/UnableToLinkException.php: -------------------------------------------------------------------------------- 1 | linkText; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Classes/Utility/CanonicalizationUtility.php: -------------------------------------------------------------------------------- 1 | getQueryParams() : []; 45 | $GET['id'] = $pageId; 46 | 47 | $queryString = HttpUtility::buildQueryString($GET, '&'); 48 | $cHashArray = $cacheHashCalculator->getRelevantParameters($queryString); 49 | 50 | // By exploding the earlier imploded array, we get the flat array with URL params 51 | $urlParameters = GeneralUtility::explodeUrl2Array($queryString); 52 | 53 | $paramsToExclude = array_keys( 54 | array_diff( 55 | $urlParameters, 56 | $cHashArray 57 | ) 58 | ); 59 | 60 | return array_diff($paramsToExclude, $additionalCanonicalizedUrlParameters); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Classes/Utility/CompressionUtility.php: -------------------------------------------------------------------------------- 1 | contentLength += strlen($outputBuffer); 54 | // Check if this was the last content chunk 55 | if (0 != ($mode & PHP_OUTPUT_HANDLER_END)) { 56 | // Check if we have content-length header 57 | foreach (headers_list() as $header) { 58 | if (strncasecmp('Content-length:', $header, 15) == 0) { 59 | header('Content-length: ' . $this->contentLength); 60 | break; 61 | } 62 | } 63 | } 64 | } 65 | return $outputBuffer; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Configuration/ContentSecurityPolicies.php: -------------------------------------------------------------------------------- 1 | ` - but NOT having the possibility to use any other assets/files/URIs) 25 | new Mutation(MutationMode::Set, Directive::StyleSrcAttr, SourceKeyword::unsafeInline), 26 | // allow `data:` images 27 | new Mutation(MutationMode::Extend, Directive::ImgSrc, SourceScheme::data), 28 | // limits `` element to be use just for same-origin URIs 29 | new Mutation(MutationMode::Set, Directive::BaseUri, SourceKeyword::self), 30 | 31 | // Allows to fetch media assets from YouTube and Vimeo and their associated CDNs, 32 | // to be embedded in an `