[TASK] Move Slug Candidate functionality into its own class
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / FormProtection / FormProtectionFactory.php
1 <?php
2 namespace TYPO3\CMS\Core\FormProtection;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
18 use TYPO3\CMS\Core\Localization\LanguageService;
19 use TYPO3\CMS\Core\Messaging\FlashMessage;
20 use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
21 use TYPO3\CMS\Core\Messaging\FlashMessageService;
22 use TYPO3\CMS\Core\Registry;
23 use TYPO3\CMS\Core\Utility\GeneralUtility;
24 use TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication;
25
26 /**
27 * This class creates and manages instances of the various form protection
28 * classes.
29 *
30 * This class provides only static methods. It can not be instantiated.
31 *
32 * Usage for the back-end form protection:
33 *
34 * <pre>
35 * $formProtection = \TYPO3\CMS\Core\FormProtection\FormProtectionFactory::get();
36 * </pre>
37 *
38 * Usage for the install tool form protection:
39 *
40 * <pre>
41 * $formProtection = \TYPO3\CMS\Core\FormProtection\FormProtectionFactory::get();
42 * </pre>
43 */
44 class FormProtectionFactory
45 {
46 /**
47 * created instances of form protections using the type as array key
48 *
49 * @var array<AbstractFormProtection>
50 */
51 protected static $instances = [];
52
53 /**
54 * Private constructor to prevent instantiation.
55 */
56 private function __construct()
57 {
58 }
59
60 /**
61 * Gets a form protection instance for the requested type or class.
62 *
63 * If there already is an existing instance of the requested $classNameOrType, the
64 * existing instance will be returned. If no $classNameOrType is provided, the factory
65 * detects the scope and returns the appropriate form protection object.
66 *
67 * @param string $classNameOrType Name of a form protection class, or one
68 * of the pre-defined form protection types:
69 * frontend, backend, installtool
70 * @param array<int, mixed> $constructorArguments Arguments for the class-constructor
71 * @return \TYPO3\CMS\Core\FormProtection\AbstractFormProtection the requested instance
72 */
73 public static function get($classNameOrType = 'default', ...$constructorArguments)
74 {
75 if (isset(self::$instances[$classNameOrType])) {
76 return self::$instances[$classNameOrType];
77 }
78 if ($classNameOrType === 'default' || $classNameOrType === 'installtool' || $classNameOrType === 'frontend' || $classNameOrType === 'backend') {
79 $classNameAndConstructorArguments = self::getClassNameAndConstructorArgumentsByType($classNameOrType);
80 self::$instances[$classNameOrType] = self::createInstance(...$classNameAndConstructorArguments);
81 } else {
82 self::$instances[$classNameOrType] = self::createInstance($classNameOrType, ...$constructorArguments);
83 }
84 return self::$instances[$classNameOrType];
85 }
86
87 /**
88 * Returns the class name and parameters depending on the given type.
89 * If the type cannot be used currently, protection is disabled.
90 *
91 * @param string $type Valid types: default, installtool, frontend, backend. "default" makes an autodection on the current state
92 * @return array Array of arguments
93 */
94 protected static function getClassNameAndConstructorArgumentsByType($type)
95 {
96 if (self::isInstallToolSession() && ($type === 'default' || $type === 'installtool')) {
97 $classNameAndConstructorArguments = [
98 InstallToolFormProtection::class
99 ];
100 } elseif (self::isFrontendSession() && ($type === 'default' || $type === 'frontend')) {
101 $classNameAndConstructorArguments = [
102 FrontendFormProtection::class,
103 $GLOBALS['TSFE']->fe_user
104 ];
105 } elseif (self::isBackendSession() && ($type === 'default' || $type === 'backend')) {
106 $classNameAndConstructorArguments = [
107 BackendFormProtection::class,
108 $GLOBALS['BE_USER'],
109 GeneralUtility::makeInstance(Registry::class),
110 self::getMessageClosure(
111 $GLOBALS['LANG'],
112 GeneralUtility::makeInstance(FlashMessageService::class)->getMessageQueueByIdentifier(),
113 (bool)(TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_AJAX)
114 )
115 ];
116 } else {
117 // failed to use preferred type, disable form protection
118 $classNameAndConstructorArguments = [
119 DisabledFormProtection::class
120 ];
121 }
122 return $classNameAndConstructorArguments;
123 }
124
125 /**
126 * Check if we are in the install tool
127 *
128 * @return bool
129 */
130 protected static function isInstallToolSession()
131 {
132 return TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_INSTALL;
133 }
134
135 /**
136 * Checks if a user is logged in and the session is active.
137 *
138 * @return bool
139 */
140 protected static function isBackendSession()
141 {
142 return isset($GLOBALS['BE_USER']) && $GLOBALS['BE_USER'] instanceof BackendUserAuthentication && isset($GLOBALS['BE_USER']->user['uid']);
143 }
144
145 /**
146 * Checks if a frontend user is logged in and the session is active.
147 *
148 * @return bool
149 */
150 protected static function isFrontendSession()
151 {
152 return TYPO3_MODE === 'FE' && is_object($GLOBALS['TSFE']) && $GLOBALS['TSFE']->fe_user instanceof FrontendUserAuthentication && isset($GLOBALS['TSFE']->fe_user->user['uid']);
153 }
154
155 /**
156 * @param LanguageService $languageService
157 * @param FlashMessageQueue $messageQueue
158 * @param bool $isAjaxCall
159 * @internal Only public to be used in tests
160 * @return \Closure
161 */
162 public static function getMessageClosure(LanguageService $languageService, FlashMessageQueue $messageQueue, $isAjaxCall)
163 {
164 return function () use ($languageService, $messageQueue, $isAjaxCall) {
165 /** @var FlashMessage $flashMessage */
166 $flashMessage = GeneralUtility::makeInstance(
167 FlashMessage::class,
168 $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:error.formProtection.tokenInvalid'),
169 '',
170 FlashMessage::ERROR,
171 !$isAjaxCall
172 );
173 $messageQueue->enqueue($flashMessage);
174 };
175 }
176
177 /**
178 * Creates an instance for the requested class $className
179 * and stores it internally.
180 *
181 * @param string $className
182 * @param array<int, mixed> $constructorArguments
183 * @throws \InvalidArgumentException
184 * @return AbstractFormProtection
185 */
186 protected static function createInstance($className, ...$constructorArguments)
187 {
188 if (!class_exists($className)) {
189 throw new \InvalidArgumentException('$className must be the name of an existing class, but actually was "' . $className . '".', 1285352962);
190 }
191 $instance = GeneralUtility::makeInstance($className, ...$constructorArguments);
192 if (!$instance instanceof AbstractFormProtection) {
193 throw new \InvalidArgumentException('$className must be a subclass of ' . AbstractFormProtection::class . ', but actually was "' . $className . '".', 1285353026);
194 }
195 return $instance;
196 }
197
198 /**
199 * Sets the instance that will be returned by get() for a specific class
200 * name.
201 *
202 * Note: This function is intended for testing purposes only.
203 *
204 * @internal
205 * @param string $classNameOrType
206 * @param AbstractFormProtection $instance
207 */
208 public static function set($classNameOrType, AbstractFormProtection $instance)
209 {
210 self::$instances[$classNameOrType] = $instance;
211 }
212
213 /**
214 * Purges all existing instances.
215 *
216 * This function is particularly useful when cleaning up in unit testing.
217 */
218 public static function purgeInstances()
219 {
220 foreach (self::$instances as $key => $instance) {
221 unset(self::$instances[$key]);
222 }
223 }
224 }