‪TYPO3CMS  ‪main
GeneralUtility.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
20 use Egulias\EmailValidator\EmailValidator;
21 use Egulias\EmailValidator\Validation\EmailValidation;
22 use Egulias\EmailValidator\Validation\MultipleValidationWithAnd;
23 use Egulias\EmailValidator\Validation\RFCValidation;
24 use GuzzleHttp\Exception\RequestException;
25 use Psr\Container\ContainerInterface;
26 use Psr\Http\Message\ServerRequestInterface;
27 use Psr\Log\LoggerAwareInterface;
28 use Psr\Log\LoggerInterface;
38 
51 {
52  protected static ?ContainerInterface ‪$container = null;
53 
59  protected static array ‪$singletonInstances = [];
60 
66  protected static array ‪$nonSingletonInstances = [];
67 
74  protected static array ‪$finalClassNameCache = [];
75 
79  protected static array ‪$indpEnvCache = [];
80 
81  final private function ‪__construct()
82  {
83  }
84 
85  /*************************
86  *
87  * GET/POST Variables
88  *
89  * Background:
90  * Input GET/POST variables in PHP may have their quotes escaped with "\" or not depending on configuration.
91  * TYPO3 has always converted quotes to BE escaped if the configuration told that they would not be so.
92  * But the clean solution is that quotes are never escaped and that is what the functions below offers.
93  * Eventually TYPO3 should provide this in the global space as well.
94  * In the transitional phase (or forever..?) we need to encourage EVERY to read and write GET/POST vars through the API functions below.
95  * This functionality was previously needed to normalize between magic quotes logic, which was removed from PHP 5.4,
96  * so these methods are still in use, but not tackle the slash problem anymore.
97  *
98  *************************/
107  public static function ‪_GP($var)
108  {
109  trigger_error(
110  'GeneralUtility::_GP() will be removed in TYPO3 v13.0, retrieve request related' .
111  ' details from PSR-7 ServerRequestInterface instead.',
112  E_USER_DEPRECATED
113  );
114  if (empty($var)) {
115  return;
116  }
117 
118  $value = $_POST[$var] ?? $_GET[$var] ?? null;
119 
120  // This is there for backwards-compatibility, in order to avoid NULL
121  if (isset($value) && !is_array($value)) {
122  $value = (string)$value;
123  }
124  return $value;
125  }
126 
134  public static function ‪_GPmerged($parameter)
135  {
136  trigger_error(
137  'GeneralUtility::_GPmerged() will be removed in TYPO3 v13.0, retrieve request related' .
138  ' details from PSR-7 ServerRequestInterface instead.',
139  E_USER_DEPRECATED
140  );
141  $postParameter = isset($_POST[$parameter]) && is_array($_POST[$parameter]) ? $_POST[$parameter] : [];
142  $getParameter = isset($_GET[$parameter]) && is_array($_GET[$parameter]) ? $_GET[$parameter] : [];
143  $mergedParameters = $getParameter;
144  ArrayUtility::mergeRecursiveWithOverrule($mergedParameters, $postParameter);
145  return $mergedParameters;
146  }
147 
155  public static function ‪_GET($var = null)
156  {
157  $value = $var === null
158  ? $_GET
159  : (empty($var) ? null : ($_GET[$var] ?? null));
160  // This is there for backwards-compatibility, in order to avoid NULL
161  if (isset($value) && !is_array($value)) {
162  $value = (string)$value;
163  }
164  return $value;
165  }
166 
174  public static function ‪_POST($var = null)
175  {
176  trigger_error(
177  'GeneralUtility::_POST() will be removed in TYPO3 v13.0, retrieve request related' .
178  ' details from PSR-7 ServerRequestInterface instead.',
179  E_USER_DEPRECATED
180  );
181  $value = $var === null ? $_POST : (empty($var) || !isset($_POST[$var]) ? null : $_POST[$var]);
182  // This is there for backwards-compatibility, in order to avoid NULL
183  if (isset($value) && !is_array($value)) {
184  $value = (string)$value;
185  }
186  return $value;
187  }
188 
189  /*************************
190  *
191  * STRING FUNCTIONS
192  *
193  *************************/
202  public static function ‪fixed_lgd_cs(string $string, int $chars, string $appendString = '...'): string
203  {
204  if ($chars === 0 || mb_strlen($string, 'utf-8') <= abs($chars)) {
205  return $string;
206  }
207  if ($chars > 0) {
208  $string = mb_substr($string, 0, $chars, 'utf-8') . $appendString;
209  } else {
210  $string = $appendString . mb_substr($string, $chars, mb_strlen($string, 'utf-8'), 'utf-8');
211  }
212  return $string;
213  }
214 
223  public static function ‪cmpIP($baseIP, $list): bool
224  {
225  $list = trim($list);
226  if ($list === '') {
227  return false;
228  }
229  if ($list === '*') {
230  return true;
231  }
232  if (str_contains($baseIP, ':') && self::validIPv6($baseIP)) {
233  return ‪self::cmpIPv6($baseIP, $list);
234  }
235  return ‪self::cmpIPv4($baseIP, $list);
236  }
237 
245  public static function ‪cmpIPv4($baseIP, $list): bool
246  {
247  $IPpartsReq = explode('.', $baseIP);
248  if (count($IPpartsReq) === 4) {
249  $values = ‪self::trimExplode(',', $list, true);
250  foreach ($values as $test) {
251  $testList = explode('/', $test);
252  if (count($testList) === 2) {
253  [$test, $mask] = $testList;
254  } else {
255  $mask = false;
256  }
257  if ((int)$mask) {
258  $mask = (int)$mask;
259  // "192.168.3.0/24"
260  $lnet = (int)ip2long($test);
261  $lip = (int)ip2long($baseIP);
262  $binnet = str_pad(decbin($lnet), 32, '0', STR_PAD_LEFT);
263  $firstpart = substr($binnet, 0, $mask);
264  $binip = str_pad(decbin($lip), 32, '0', STR_PAD_LEFT);
265  $firstip = substr($binip, 0, $mask);
266  $yes = $firstpart === $firstip;
267  } else {
268  // "192.168.*.*"
269  $IPparts = explode('.', $test);
270  $yes = 1;
271  foreach ($IPparts as $index => $val) {
272  $val = trim($val);
273  if ($val !== '*' && $IPpartsReq[$index] !== $val) {
274  $yes = 0;
275  }
276  }
277  }
278  if ($yes) {
279  return true;
280  }
281  }
282  }
283  return false;
284  }
285 
294  public static function ‪cmpIPv6($baseIP, $list): bool
295  {
296  // Policy default: Deny connection
297  $success = false;
298  $baseIP = ‪self::normalizeIPv6($baseIP);
299  $values = ‪self::trimExplode(',', $list, true);
300  foreach ($values as $test) {
301  $testList = explode('/', $test);
302  if (count($testList) === 2) {
303  [$test, $mask] = $testList;
304  } else {
305  $mask = false;
306  }
307  if (self::validIPv6($test)) {
308  $test = ‪self::normalizeIPv6($test);
309  $maskInt = (int)$mask ?: 128;
310  // Special case; /0 is an allowed mask - equals a wildcard
311  if ($mask === '0') {
312  $success = true;
313  } elseif ($maskInt == 128) {
314  $success = $test === $baseIP;
315  } else {
316  $testBin = (string)inet_pton($test);
317  $baseIPBin = (string)inet_pton($baseIP);
318 
319  $success = true;
320  // Modulo is 0 if this is a 8-bit-boundary
321  $maskIntModulo = $maskInt % 8;
322  $numFullCharactersUntilBoundary = (int)($maskInt / 8);
323  $substring = (string)substr($baseIPBin, 0, $numFullCharactersUntilBoundary);
324  if (!str_starts_with($testBin, $substring)) {
325  $success = false;
326  } elseif ($maskIntModulo > 0) {
327  // If not an 8-bit-boundary, check bits of last character
328  $testLastBits = str_pad(decbin(ord(substr($testBin, $numFullCharactersUntilBoundary, 1))), 8, '0', STR_PAD_LEFT);
329  $baseIPLastBits = str_pad(decbin(ord(substr($baseIPBin, $numFullCharactersUntilBoundary, 1))), 8, '0', STR_PAD_LEFT);
330  if (strncmp($testLastBits, $baseIPLastBits, $maskIntModulo) != 0) {
331  $success = false;
332  }
333  }
334  }
335  }
336  if ($success) {
337  return true;
338  }
339  }
340  return false;
341  }
342 
349  public static function ‪normalizeIPv6($address): string
350  {
351  $normalizedAddress = '';
352  // According to RFC lowercase-representation is recommended
353  $address = strtolower($address);
354  // Normalized representation has 39 characters (0000:0000:0000:0000:0000:0000:0000:0000)
355  if (strlen($address) === 39) {
356  // Already in full expanded form
357  return $address;
358  }
359  // Count 2 if if address has hidden zero blocks
360  $chunks = explode('::', $address);
361  if (count($chunks) === 2) {
362  $chunksLeft = explode(':', $chunks[0]);
363  $chunksRight = explode(':', $chunks[1]);
364  $left = count($chunksLeft);
365  $right = count($chunksRight);
366  // Special case: leading zero-only blocks count to 1, should be 0
367  if ($left === 1 && strlen($chunksLeft[0]) === 0) {
368  $left = 0;
369  }
370  $hiddenBlocks = 8 - ($left + $right);
371  $hiddenPart = '';
372  $h = 0;
373  while ($h < $hiddenBlocks) {
374  $hiddenPart .= '0000:';
375  $h++;
376  }
377  if ($left === 0) {
378  $stageOneAddress = $hiddenPart . $chunks[1];
379  } else {
380  $stageOneAddress = $chunks[0] . ':' . $hiddenPart . $chunks[1];
381  }
382  } else {
383  $stageOneAddress = $address;
384  }
385  // Normalize the blocks:
386  $blocks = explode(':', $stageOneAddress);
387  $divCounter = 0;
388  foreach ($blocks as $block) {
389  $tmpBlock = '';
390  $i = 0;
391  $hiddenZeros = 4 - strlen($block);
392  while ($i < $hiddenZeros) {
393  $tmpBlock .= '0';
394  $i++;
395  }
396  $normalizedAddress .= $tmpBlock . $block;
397  if ($divCounter < 7) {
398  $normalizedAddress .= ':';
399  $divCounter++;
400  }
401  }
402  return $normalizedAddress;
403  }
404 
413  public static function ‪validIP($ip): bool
414  {
415  return filter_var($ip, FILTER_VALIDATE_IP) !== false;
416  }
417 
426  public static function ‪validIPv4($ip): bool
427  {
428  return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false;
429  }
430 
439  public static function ‪validIPv6($ip): bool
440  {
441  return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false;
442  }
443 
451  public static function ‪cmpFQDN($baseHost, $list)
452  {
453  $baseHost = trim($baseHost);
454  if (empty($baseHost)) {
455  return false;
456  }
457  if (self::validIPv4($baseHost) || self::validIPv6($baseHost)) {
458  // Resolve hostname
459  // Note: this is reverse-lookup and can be randomly set as soon as somebody is able to set
460  // the reverse-DNS for his IP (security when for example used with REMOTE_ADDR)
461  $baseHostName = (string)gethostbyaddr($baseHost);
462  if ($baseHostName === $baseHost) {
463  // Unable to resolve hostname
464  return false;
465  }
466  } else {
467  $baseHostName = $baseHost;
468  }
469  $baseHostNameParts = explode('.', $baseHostName);
470  $values = ‪self::trimExplode(',', $list, true);
471  foreach ($values as $test) {
472  $hostNameParts = explode('.', $test);
473  // To match hostNameParts can only be shorter (in case of wildcards) or equal
474  $hostNamePartsCount = count($hostNameParts);
475  $baseHostNamePartsCount = count($baseHostNameParts);
476  if ($hostNamePartsCount > $baseHostNamePartsCount) {
477  continue;
478  }
479  $yes = true;
480  foreach ($hostNameParts as $index => $val) {
481  $val = trim($val);
482  if ($val === '*') {
483  // Wildcard valid for one or more hostname-parts
484  $wildcardStart = $index + 1;
485  // Wildcard as last/only part always matches, otherwise perform recursive checks
486  if ($wildcardStart < $hostNamePartsCount) {
487  $wildcardMatched = false;
488  $tempHostName = implode('.', array_slice($hostNameParts, $index + 1));
489  while ($wildcardStart < $baseHostNamePartsCount && !$wildcardMatched) {
490  $tempBaseHostName = implode('.', array_slice($baseHostNameParts, $wildcardStart));
491  $wildcardMatched = ‪self::cmpFQDN($tempBaseHostName, $tempHostName);
492  $wildcardStart++;
493  }
494  if ($wildcardMatched) {
495  // Match found by recursive compare
496  return true;
497  }
498  $yes = false;
499  }
500  } elseif ($baseHostNameParts[$index] !== $val) {
501  // In case of no match
502  $yes = false;
503  }
504  }
505  if ($yes) {
506  return true;
507  }
508  }
509  return false;
510  }
511 
519  public static function ‪isOnCurrentHost(‪$url)
520  {
521  return stripos(‪$url . '/', self::getIndpEnv('TYPO3_REQUEST_HOST') . '/') === 0;
522  }
523 
532  public static function ‪inList($list, $item)
533  {
534  return str_contains(',' . $list . ',', ',' . $item . ',');
535  }
536 
544  public static function ‪expandList($list)
545  {
546  $items = explode(',', $list);
547  $list = [];
548  foreach ($items as $item) {
549  $range = explode('-', $item);
550  if (isset($range[1])) {
551  $runAwayBrake = 1000;
552  for ($n = $range[0]; $n <= $range[1]; $n++) {
553  $list[] = $n;
554  $runAwayBrake--;
555  if ($runAwayBrake <= 0) {
556  break;
557  }
558  }
559  } else {
560  $list[] = $item;
561  }
562  }
563  return implode(',', $list);
564  }
565 
572  public static function ‪md5int($str)
573  {
574  return hexdec(substr(md5($str), 0, 7));
575  }
576 
584  public static function ‪hmac($input, $additionalSecret = '')
585  {
586  $hashAlgorithm = 'sha1';
587  $secret = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'] . $additionalSecret;
588  return hash_hmac($hashAlgorithm, $input, $secret);
589  }
590 
597  public static function split_fileref($fileNameWithPath)
598  {
599  $info = [];
600  $reg = [];
601  if (preg_match('/(.*\\/)(.*)$/', $fileNameWithPath, $reg)) {
602  $info['path'] = $reg[1];
603  $info['file'] = $reg[2];
604  } else {
605  $info['path'] = '';
606  $info['file'] = $fileNameWithPath;
607  }
608  $reg = '';
609  // If open_basedir is set and the fileName was supplied without a path the is_dir check fails
610  if (!is_dir($fileNameWithPath) && preg_match('/(.*)\\.([^\\.]*$)/', $info['file'], $reg)) {
611  $info['filebody'] = $reg[1];
612  $info['fileext'] = strtolower($reg[2]);
613  $info['realFileext'] = $reg[2];
614  } else {
615  $info['filebody'] = $info['file'];
616  $info['fileext'] = '';
617  }
618  reset($info);
619  return $info;
620  }
621 
637  public static function dirname($path)
638  {
639  $p = ‪self::revExplode('/', $path, 2);
640  return count($p) === 2 ? $p[0] : '';
641  }
642 
651  public static function formatSize(‪$sizeInBytes, $labels = '', $base = 0)
652  {
653  $defaultFormats = [
654  'iec' => ['base' => 1024, 'labels' => [' ', ' Ki', ' Mi', ' Gi', ' Ti', ' Pi', ' Ei', ' Zi', ' Yi']],
655  'si' => ['base' => 1000, 'labels' => [' ', ' k', ' M', ' G', ' T', ' P', ' E', ' Z', ' Y']],
656  ];
657  // Set labels and base:
658  if (empty($labels)) {
659  $labels = 'iec';
660  }
661  if (isset($defaultFormats[$labels])) {
662  $base = $defaultFormats[$labels]['base'];
663  ‪$labelArr = $defaultFormats[$labels]['labels'];
664  } else {
665  $base = (int)$base;
666  if ($base !== 1000 && $base !== 1024) {
667  $base = 1024;
668  }
669  ‪$labelArr = explode('|', str_replace('"', '', $labels));
670  }
671  // This is set via Site Handling and in the Locales class via setlocale()
672  ‪$localeInfo = localeconv();
674  ‪$multiplier = floor((‪$sizeInBytes ? log(‪$sizeInBytes) : 0) / log($base));
676  if (‪$sizeInUnits > ($base * .9)) {
677  ‪$multiplier++;
678  }
681  return number_format(‪$sizeInUnits, ((‪$multiplier > 0) && (‪$sizeInUnits < 20)) ? 2 : 0, ‪$localeInfo['decimal_point'], '') . ‪$labelArr[‪$multiplier];
682  }
683 
693  public static function splitCalc($string, $operators)
694  {
695  $res = [];
696  $sign = '+';
697  while ($string) {
698  $valueLen = strcspn($string, $operators);
699  $value = substr($string, 0, $valueLen);
700  $res[] = [$sign, trim($value)];
701  $sign = substr($string, $valueLen, 1);
702  $string = substr($string, $valueLen + 1);
703  }
704  reset($res);
705  return $res;
706  }
707 
714  public static function validEmail($email)
715  {
716  // Early return in case input is not a string
717  if (!is_string($email)) {
718  return false;
719  }
720  if (trim($email) !== $email) {
721  return false;
722  }
723  if (!str_contains($email, '@')) {
724  return false;
725  }
726  $validators = [];
727  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['MAIL']['validators'] ?? [RFCValidation::class] as $className) {
728  ‪$validator = new $className();
729  if (‪$validator instanceof EmailValidation) {
730  $validators[] = ‪$validator;
731  }
732  }
733  return (new EmailValidator())->isValid($email, new MultipleValidationWithAnd($validators, MultipleValidationWithAnd::STOP_ON_ERROR));
734  }
735 
743  public static function ‪underscoredToUpperCamelCase($string)
744  {
745  return str_replace(' ', '', ucwords(str_replace('_', ' ', strtolower($string))));
746  }
747 
755  public static function ‪underscoredToLowerCamelCase($string)
756  {
757  return lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', strtolower($string)))));
758  }
759 
767  public static function ‪camelCaseToLowerCaseUnderscored($string)
768  {
769  $value = preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $string) ?? '';
770  return mb_strtolower($value, 'utf-8');
771  }
772 
797  public static function ‪isValidUrl(‪$url)
798  {
799  $parsedUrl = parse_url(‪$url);
800  if (!$parsedUrl || !isset($parsedUrl['scheme'])) {
801  return false;
802  }
803  // HttpUtility::buildUrl() will always build urls with <scheme>://
804  // our original $url might only contain <scheme>: (e.g. mail:)
805  // so we convert that to the double-slashed version to ensure
806  // our check against the $recomposedUrl is proper
807  if (!str_starts_with(‪$url, $parsedUrl['scheme'] . '://')) {
808  ‪$url = str_replace($parsedUrl['scheme'] . ':', $parsedUrl['scheme'] . '://', ‪$url);
809  }
810  $recomposedUrl = ‪HttpUtility::buildUrl($parsedUrl);
811  if ($recomposedUrl !== ‪$url) {
812  // The parse_url() had to modify characters, so the URL is invalid
813  return false;
814  }
815  if (isset($parsedUrl['host']) && !preg_match('/^[a-z0-9.\\-]*$/i', $parsedUrl['host'])) {
816  $host = idn_to_ascii($parsedUrl['host']);
817  if ($host === false) {
818  return false;
819  }
820  $parsedUrl['host'] = $host;
821  }
822  return filter_var(‪HttpUtility::buildUrl($parsedUrl), FILTER_VALIDATE_URL) !== false;
823  }
824 
825  /*************************
826  *
827  * ARRAY FUNCTIONS
828  *
829  *************************/
830 
842  public static function ‪intExplode($delimiter, $string, $removeEmptyValues = false, $limit = 0)
843  {
844  $result = explode($delimiter, $string);
845  foreach ($result as $key => &$value) {
846  if ($removeEmptyValues && trim($value) === '') {
847  unset($result[$key]);
848  } else {
849  $value = (int)$value;
850  }
851  }
852  unset($value);
853  if ($limit !== 0) {
854  trigger_error('The parameter $limit will be removed from ' . __METHOD__ . ' in TYPO3 v13.0.', E_USER_DEPRECATED);
855 
856  if ($limit < 0) {
857  $result = array_slice($result, 0, $limit);
858  } elseif (count($result) > $limit) {
859  $lastElements = array_slice($result, $limit - 1);
860  $result = array_slice($result, 0, $limit - 1);
861  $result[] = implode($delimiter, $lastElements);
862  }
863  }
864  return $result;
865  }
866 
882  public static function ‪revExplode($delimiter, $string, $limit = 0)
883  {
884  // 2 is the (currently, as of 2014-02) most-used value for `$limit` in the core, therefore we check it first
885  if ($limit === 2) {
886  $position = strrpos($string, strrev($delimiter));
887  if ($position !== false) {
888  return [substr($string, 0, $position), substr($string, $position + strlen($delimiter))];
889  }
890  return [$string];
891  }
892  if ($limit <= 1) {
893  return [$string];
894  }
895  $explodedValues = explode($delimiter, strrev($string), $limit);
896  $explodedValues = array_map(strrev(...), $explodedValues);
897  return array_reverse($explodedValues);
898  }
899 
916  public static function ‪trimExplode($delim, $string, $removeEmptyValues = false, $limit = 0): array
917  {
918  $result = explode($delim, (string)$string);
919  if ($removeEmptyValues) {
920  // Remove items that are just whitespace, but leave whitespace intact for the rest.
921  $result = array_values(array_filter($result, static fn (string $item): bool => trim($item) !== ''));
922  }
923 
924  if ($limit === 0) {
925  // Return everything.
926  return array_map(trim(...), $result);
927  }
928 
929  if ($limit < 0) {
930  // Trim and return just the first $limit elements and ignore the rest.
931  return array_map(trim(...), array_slice($result, 0, $limit));
932  }
933 
934  // Fold the last length - $limit elements into a single trailing item, then trim and return the result.
935  $tail = array_slice($result, $limit - 1);
936  $result = array_slice($result, 0, $limit - 1);
937  if ($tail) {
938  $result[] = implode($delim, $tail);
939  }
940  return array_map(trim(...), $result);
941  }
942 
954  public static function ‪implodeArrayForUrl($name, array $theArray, $str = '', $skipBlank = false, $rawurlencodeParamName = false)
955  {
956  foreach ($theArray as $Akey => $AVal) {
957  $thisKeyName = $name ? $name . '[' . $Akey . ']' : $Akey;
958  if (is_array($AVal)) {
959  $str = ‪self::implodeArrayForUrl($thisKeyName, $AVal, $str, $skipBlank, $rawurlencodeParamName);
960  } else {
961  $stringValue = (string)$AVal;
962  if (!$skipBlank || $stringValue !== '') {
963  $parameterName = $rawurlencodeParamName ? rawurlencode($thisKeyName) : $thisKeyName;
964  $parameterValue = rawurlencode($stringValue);
965  $str .= '&' . $parameterName . '=' . $parameterValue;
966  }
967  }
968  }
969  return $str;
970  }
971 
987  public static function explodeUrl2Array($string)
988  {
989  ‪$output = [];
990  $p = explode('&', $string);
991  foreach ($p as $v) {
992  if ($v !== '') {
993  $nameAndValue = explode('=', $v, 2);
994  ‪$output[rawurldecode($nameAndValue[0])] = isset($nameAndValue[1]) ? rawurldecode($nameAndValue[1]) : '';
995  }
996  }
997  return ‪$output;
998  }
999 
1007  public static function removeDotsFromTS(array $ts): array
1008  {
1009  $out = [];
1010  foreach ($ts as $key => $value) {
1011  if (is_array($value)) {
1012  $key = rtrim($key, '.');
1013  $out[$key] = self::removeDotsFromTS($value);
1014  } else {
1015  $out[$key] = $value;
1016  }
1017  }
1018  return $out;
1019  }
1020 
1021  /*************************
1022  *
1023  * HTML/XML PROCESSING
1024  *
1025  *************************/
1035  public static function get_tag_attributes($tag, bool $decodeEntities = false)
1036  {
1037  $components = self::split_tag_attributes($tag);
1038  // Attribute name is stored here
1039  $name = '';
1040  $valuemode = false;
1041  $attributes = [];
1042  foreach ($components as $key => $val) {
1043  // Only if $name is set (if there is an attribute, that waits for a value), that valuemode is enabled. This ensures that the attribute is assigned it's value
1044  if ($val !== '=') {
1045  if ($valuemode) {
1046  if ($name) {
1047  $attributes[$name] = $decodeEntities ? htmlspecialchars_decode($val) : $val;
1048  $name = '';
1049  }
1050  } else {
1051  if ($key = strtolower(preg_replace('/[^[:alnum:]_\\:\\-]/', '', $val) ?? '')) {
1052  $attributes[$key] = '';
1053  $name = $key;
1054  }
1055  }
1056  $valuemode = false;
1057  } else {
1058  $valuemode = true;
1059  }
1060  }
1061  return $attributes;
1062  }
1063 
1071  public static function split_tag_attributes($tag)
1072  {
1073  $tag_tmp = trim(preg_replace('/^<[^[:space:]]*/', '', trim($tag)) ?? '');
1074  // Removes any > in the end of the string
1075  $tag_tmp = trim(rtrim($tag_tmp, '>'));
1076  $value = [];
1077  // Compared with empty string instead , 030102
1078  while ($tag_tmp !== '') {
1079  $firstChar = $tag_tmp[0];
1080  if ($firstChar === '"' || $firstChar === '\'') {
1081  $reg = explode($firstChar, $tag_tmp, 3);
1082  $value[] = $reg[1];
1083  $tag_tmp = trim($reg[2] ?? '');
1084  } elseif ($firstChar === '=') {
1085  $value[] = '=';
1086  // Removes = chars.
1087  $tag_tmp = trim(substr($tag_tmp, 1));
1088  } else {
1089  // There are '' around the value. We look for the next ' ' or '>'
1090  $reg = preg_split('/[[:space:]=]/', $tag_tmp, 2);
1091  $value[] = trim($reg[0]);
1092  $tag_tmp = trim(substr($tag_tmp, strlen($reg[0]), 1) . ($reg[1] ?? ''));
1093  }
1094  }
1095  reset($value);
1096  return $value;
1097  }
1098 
1107  public static function implodeAttributes(array $arr, $xhtmlSafe = false, $keepBlankAttributes = false)
1108  {
1109  if ($xhtmlSafe) {
1110  $newArr = [];
1111  foreach ($arr as $attributeName => $attributeValue) {
1112  $attributeName = strtolower($attributeName);
1113  if (!isset($newArr[$attributeName])) {
1114  $newArr[$attributeName] = htmlspecialchars((string)$attributeValue);
1115  }
1116  }
1117  $arr = $newArr;
1118  }
1119  $list = [];
1120  foreach ($arr as $attributeName => $attributeValue) {
1121  if ((string)$attributeValue !== '' || $keepBlankAttributes) {
1122  $list[] = $attributeName . '="' . $attributeValue . '"';
1123  }
1124  }
1125  return implode(' ', $list);
1126  }
1127 
1137  public static function wrapJS(string $string, array $attributes = [])
1138  {
1139  if (trim($string)) {
1140  // remove nl from the beginning
1141  $string = ltrim($string, LF);
1142  // re-ident to one tab using the first line as reference
1143  $match = [];
1144  if (preg_match('/^(\\t+)/', $string, $match)) {
1145  $string = str_replace($match[1], "\t", $string);
1146  }
1147  return '<script ' . GeneralUtility::implodeAttributes($attributes, true) . '>
1148 /*<![CDATA[*/
1149 ' . $string . '
1150 /*]]>*/
1151 </script>';
1152  }
1153  return '';
1154  }
1155 
1164  public static function xml2tree($string, $depth = 999, $parserOptions = [])
1165  {
1166  ‪$parser = xml_parser_create();
1167  $vals = [];
1168  $index = [];
1169  xml_parser_set_option(‪$parser, XML_OPTION_CASE_FOLDING, 0);
1170  xml_parser_set_option(‪$parser, XML_OPTION_SKIP_WHITE, 0);
1171  foreach ($parserOptions as $option => $value) {
1172  xml_parser_set_option(‪$parser, $option, $value);
1173  }
1174  xml_parse_into_struct(‪$parser, $string, $vals, $index);
1175  if (xml_get_error_code(‪$parser)) {
1176  return 'Line ' . xml_get_current_line_number(‪$parser) . ': ' . xml_error_string(xml_get_error_code(‪$parser));
1177  }
1178  xml_parser_free(‪$parser);
1179  $stack = [[]];
1180  $stacktop = 0;
1181  $startPoint = 0;
1182  $tagi = [];
1183  foreach ($vals as $key => $val) {
1184  $type = $val['type'];
1185  // open tag:
1186  if ($type === 'open' || $type === 'complete') {
1187  $stack[$stacktop++] = $tagi;
1188  if ($depth == $stacktop) {
1189  $startPoint = $key;
1190  }
1191  $tagi = ['tag' => $val['tag']];
1192  if (isset($val['attributes'])) {
1193  $tagi['attrs'] = $val['attributes'];
1194  }
1195  if (isset($val['value'])) {
1196  $tagi['values'][] = $val['value'];
1197  }
1198  }
1199  // finish tag:
1200  if ($type === 'complete' || $type === 'close') {
1201  $oldtagi = $tagi;
1202  $tagi = $stack[--$stacktop];
1203  $oldtag = $oldtagi['tag'];
1204  unset($oldtagi['tag']);
1205  if ($depth == $stacktop + 1) {
1206  if ($key - $startPoint > 0) {
1207  $partArray = array_slice($vals, $startPoint + 1, $key - $startPoint - 1);
1208  $oldtagi['XMLvalue'] = ‪self::xmlRecompileFromStructValArray($partArray);
1209  } else {
1210  $oldtagi['XMLvalue'] = $oldtagi['values'][0];
1211  }
1212  }
1213  $tagi['ch'][$oldtag][] = $oldtagi;
1214  unset($oldtagi);
1215  }
1216  // cdata
1217  if ($type === 'cdata') {
1218  $tagi['values'][] = $val['value'];
1219  }
1220  }
1221  return $tagi['ch'];
1222  }
1223 
1244  public static function array2xml(array $array, $NSprefix = '', $level = 0, $docTag = 'phparray', ‪$spaceInd = 0, array $options = [], array $stackData = [])
1245  {
1246  // The list of byte values which will trigger binary-safe storage. If any value has one of these char values in it, it will be encoded in base64
1247  $binaryChars = "\0" . chr(1) . chr(2) . chr(3) . chr(4) . chr(5) . chr(6) . chr(7) . chr(8) . chr(11) . chr(12) . chr(14) . chr(15) . chr(16) . chr(17) . chr(18) . chr(19) . chr(20) . chr(21) . chr(22) . chr(23) . chr(24) . chr(25) . chr(26) . chr(27) . chr(28) . chr(29) . chr(30) . chr(31);
1248  // Set indenting mode:
1249  $indentChar = ‪$spaceInd ? ' ' : "\t";
1250  $indentN = ‪$spaceInd > 0 ? ‪$spaceInd : 1;
1251  ‪$nl = ‪$spaceInd >= 0 ? LF : '';
1252  // Init output variable:
1254  // Traverse the input array
1255  foreach ($array as $k => $v) {
1256  $attr = '';
1257  $tagName = (string)$k;
1258  // Construct the tag name.
1259  // Use tag based on grand-parent + parent tag name
1260  if (isset($stackData['grandParentTagName'], $stackData['parentTagName'], $options['grandParentTagMap'][$stackData['grandParentTagName'] . '/' . $stackData['parentTagName']])) {
1261  $attr .= ' index="' . htmlspecialchars($tagName) . '"';
1262  $tagName = (string)$options['grandParentTagMap'][$stackData['grandParentTagName'] . '/' . $stackData['parentTagName']];
1263  } elseif (isset($stackData['parentTagName'], $options['parentTagMap'][$stackData['parentTagName'] . ':_IS_NUM']) && ‪MathUtility::canBeInterpretedAsInteger($tagName)) {
1264  // Use tag based on parent tag name + if current tag is numeric
1265  $attr .= ' index="' . htmlspecialchars($tagName) . '"';
1266  $tagName = (string)$options['parentTagMap'][$stackData['parentTagName'] . ':_IS_NUM'];
1267  } elseif (isset($stackData['parentTagName'], $options['parentTagMap'][$stackData['parentTagName'] . ':' . $tagName])) {
1268  // Use tag based on parent tag name + current tag
1269  $attr .= ' index="' . htmlspecialchars($tagName) . '"';
1270  $tagName = (string)$options['parentTagMap'][$stackData['parentTagName'] . ':' . $tagName];
1271  } elseif (isset($stackData['parentTagName'], $options['parentTagMap'][$stackData['parentTagName']])) {
1272  // Use tag based on parent tag name:
1273  $attr .= ' index="' . htmlspecialchars($tagName) . '"';
1274  $tagName = (string)$options['parentTagMap'][$stackData['parentTagName']];
1275  } elseif (‪MathUtility::canBeInterpretedAsInteger($tagName)) {
1276  // If integer...;
1277  if ($options['useNindex'] ?? false) {
1278  // If numeric key, prefix "n"
1279  $tagName = 'n' . $tagName;
1280  } else {
1281  // Use special tag for num. keys:
1282  $attr .= ' index="' . $tagName . '"';
1283  $tagName = ($options['useIndexTagForNum'] ?? false) ?: 'numIndex';
1284  }
1285  } elseif (!empty($options['useIndexTagForAssoc'])) {
1286  // Use tag for all associative keys:
1287  $attr .= ' index="' . htmlspecialchars($tagName) . '"';
1288  $tagName = $options['useIndexTagForAssoc'];
1289  }
1290  // The tag name is cleaned up so only alphanumeric chars (plus - and _) are in there and not longer than 100 chars either.
1291  $tagName = substr(preg_replace('/[^[:alnum:]_-]/', '', $tagName), 0, 100);
1292  // If the value is an array then we will call this function recursively:
1293  if (is_array($v)) {
1294  // Sub elements:
1295  if (isset($options['alt_options']) && ($options['alt_options'][($stackData['path'] ?? '') . '/' . $tagName] ?? false)) {
1296  $subOptions = $options['alt_options'][($stackData['path'] ?? '') . '/' . $tagName];
1297  $clearStackPath = (bool)($subOptions['clearStackPath'] ?? false);
1298  } else {
1299  $subOptions = $options;
1300  $clearStackPath = false;
1301  }
1302  if (empty($v)) {
1303  $content = '';
1304  } else {
1305  $content = ‪$nl . self::array2xml($v, $NSprefix, $level + 1, '', ‪$spaceInd, $subOptions, [
1306  'parentTagName' => $tagName,
1307  'grandParentTagName' => $stackData['parentTagName'] ?? '',
1308  'path' => $clearStackPath ? '' : ($stackData['path'] ?? '') . '/' . $tagName,
1309  ]) . (‪$spaceInd >= 0 ? str_pad('', ($level + 1) * $indentN, $indentChar) : '');
1310  }
1311  // Do not set "type = array". Makes prettier XML but means that empty arrays are not restored with xml2array
1312  if (!isset($options['disableTypeAttrib']) || (int)$options['disableTypeAttrib'] != 2) {
1313  $attr .= ' type="array"';
1314  }
1315  } else {
1316  $stringValue = (string)$v;
1317  // Just a value:
1318  // Look for binary chars:
1319  $vLen = strlen($stringValue);
1320  // Go for base64 encoding if the initial segment NOT matching any binary char has the same length as the whole string!
1321  if ($vLen && strcspn($stringValue, $binaryChars) != $vLen) {
1322  // If the value contained binary chars then we base64-encode it and set an attribute to notify this situation:
1323  $content = ‪$nl . chunk_split(base64_encode($stringValue));
1324  $attr .= ' base64="1"';
1325  } else {
1326  // Otherwise, just htmlspecialchar the stuff:
1327  $content = htmlspecialchars($stringValue);
1328  $dType = gettype($v);
1329  if ($dType === 'string') {
1330  if (isset($options['useCDATA']) && $options['useCDATA'] && $content != $stringValue) {
1331  $content = '<![CDATA[' . $stringValue . ']]>';
1332  }
1333  } elseif (!($options['disableTypeAttrib'] ?? false)) {
1334  $attr .= ' type="' . $dType . '"';
1335  }
1336  }
1337  }
1338  if ($tagName !== '') {
1339  // Add the element to the output string:
1340  ‪$output .= (‪$spaceInd >= 0 ? str_pad('', ($level + 1) * $indentN, $indentChar) : '')
1341  . '<' . $NSprefix . $tagName . $attr . '>' . $content . '</' . $NSprefix . $tagName . '>' . ‪$nl;
1342  }
1343  }
1344  // If we are at the outer-most level, then we finally wrap it all in the document tags and return that as the value:
1345  if (!$level) {
1346  ‪$output = '<' . $docTag . '>' . ‪$nl . ‪$output . '</' . $docTag . '>';
1347  }
1348  return ‪$output;
1349  }
1350 
1363  public static function ‪xml2array($string, $NSprefix = '', $reportDocTag = false)
1364  {
1365  $runtimeCache = static::makeInstance(CacheManager::class)->getCache('runtime');
1366  $firstLevelCache = $runtimeCache->get('generalUtilityXml2Array') ?: [];
1367  ‪$identifier = md5($string . $NSprefix . ($reportDocTag ? '1' : '0'));
1368  // Look up in first level cache
1369  if (empty($firstLevelCache[‪$identifier])) {
1370  $firstLevelCache[‪$identifier] = ‪self::xml2arrayProcess($string, $NSprefix, $reportDocTag);
1371  $runtimeCache->set('generalUtilityXml2Array', $firstLevelCache);
1372  }
1373  return $firstLevelCache[‪$identifier];
1374  }
1375 
1386  public static function ‪xml2arrayProcess($string, $NSprefix = '', $reportDocTag = false)
1387  {
1388  $string = trim((string)$string);
1389  // Create parser:
1390  ‪$parser = xml_parser_create();
1391  $vals = [];
1392  $index = [];
1393  xml_parser_set_option(‪$parser, XML_OPTION_CASE_FOLDING, 0);
1394  xml_parser_set_option(‪$parser, XML_OPTION_SKIP_WHITE, 0);
1395  // Default output charset is UTF-8, only ASCII, ISO-8859-1 and UTF-8 are supported!!!
1396  $match = [];
1397  preg_match('/^[[:space:]]*<\\?xml[^>]*encoding[[:space:]]*=[[:space:]]*"([^"]*)"/', substr($string, 0, 200), $match);
1398  $theCharset = $match[1] ?? 'utf-8';
1399  // us-ascii / utf-8 / iso-8859-1
1400  xml_parser_set_option(‪$parser, XML_OPTION_TARGET_ENCODING, $theCharset);
1401  // Parse content:
1402  xml_parse_into_struct(‪$parser, $string, $vals, $index);
1403  // If error, return error message:
1404  if (xml_get_error_code(‪$parser)) {
1405  return 'Line ' . xml_get_current_line_number(‪$parser) . ': ' . xml_error_string(xml_get_error_code(‪$parser));
1406  }
1407  xml_parser_free(‪$parser);
1408  // Init vars:
1409  $stack = [[]];
1410  $stacktop = 0;
1411  $current = [];
1412  $tagName = '';
1413  $documentTag = '';
1414  // Traverse the parsed XML structure:
1415  foreach ($vals as $key => $val) {
1416  // First, process the tag-name (which is used in both cases, whether "complete" or "close")
1417  $tagName = $val['tag'];
1418  if (!$documentTag) {
1419  $documentTag = $tagName;
1420  }
1421  // Test for name space:
1422  $tagName = $NSprefix && str_starts_with($tagName, $NSprefix) ? substr($tagName, strlen($NSprefix)) : $tagName;
1423  // Test for numeric tag, encoded on the form "nXXX":
1424  $testNtag = substr($tagName, 1);
1425  // Closing tag.
1426  $tagName = $tagName[0] === 'n' && ‪MathUtility::canBeInterpretedAsInteger($testNtag) ? (int)$testNtag : $tagName;
1427  // Test for alternative index value:
1428  if ((string)($val['attributes']['index'] ?? '') !== '') {
1429  $tagName = $val['attributes']['index'];
1430  }
1431  // Setting tag-values, manage stack:
1432  switch ($val['type']) {
1433  case 'open':
1434  // If open tag it means there is an array stored in sub-elements. Therefore increase the stackpointer and reset the accumulation array:
1435  // Setting blank place holder
1436  $current[$tagName] = [];
1437  $stack[$stacktop++] = $current;
1438  $current = [];
1439  break;
1440  case 'close':
1441  // If the tag is "close" then it is an array which is closing and we decrease the stack pointer.
1442  $oldCurrent = $current;
1443  $current = $stack[--$stacktop];
1444  // Going to the end of array to get placeholder key, key($current), and fill in array next:
1445  end($current);
1446  $current[key($current)] = $oldCurrent;
1447  unset($oldCurrent);
1448  break;
1449  case 'complete':
1450  // If "complete", then it's a value. If the attribute "base64" is set, then decode the value, otherwise just set it.
1451  if (!empty($val['attributes']['base64'])) {
1452  $current[$tagName] = base64_decode($val['value']);
1453  } else {
1454  // Had to cast it as a string - otherwise it would be evaluate FALSE if tested with isset()!!
1455  $current[$tagName] = (string)($val['value'] ?? '');
1456  // Cast type:
1457  switch ((string)($val['attributes']['type'] ?? '')) {
1458  case 'integer':
1459  $current[$tagName] = (int)$current[$tagName];
1460  break;
1461  case 'double':
1462  $current[$tagName] = (float)$current[$tagName];
1463  break;
1464  case 'boolean':
1465  $current[$tagName] = (bool)$current[$tagName];
1466  break;
1467  case 'NULL':
1468  $current[$tagName] = null;
1469  break;
1470  case 'array':
1471  // MUST be an empty array since it is processed as a value; Empty arrays would end up here because they would have no tags inside...
1472  $current[$tagName] = [];
1473  break;
1474  }
1475  }
1476  break;
1477  }
1478  }
1479  if ($reportDocTag) {
1480  $current[$tagName]['_DOCUMENT_TAG'] = $documentTag;
1481  }
1482  // Finally return the content of the document tag.
1483  return $current[$tagName];
1484  }
1485 
1492  public static function ‪xmlRecompileFromStructValArray(array $vals)
1493  {
1494  $XMLcontent = '';
1495  foreach ($vals as $val) {
1496  $type = $val['type'];
1497  // Open tag:
1498  if ($type === 'open' || $type === 'complete') {
1499  $XMLcontent .= '<' . $val['tag'];
1500  if (isset($val['attributes'])) {
1501  foreach ($val['attributes'] as $k => $v) {
1502  $XMLcontent .= ' ' . $k . '="' . htmlspecialchars($v) . '"';
1503  }
1504  }
1505  if ($type === 'complete') {
1506  if (isset($val['value'])) {
1507  $XMLcontent .= '>' . htmlspecialchars($val['value']) . '</' . $val['tag'] . '>';
1508  } else {
1509  $XMLcontent .= '/>';
1510  }
1511  } else {
1512  $XMLcontent .= '>';
1513  }
1514  if ($type === 'open' && isset($val['value'])) {
1515  $XMLcontent .= htmlspecialchars($val['value']);
1516  }
1517  }
1518  // Finish tag:
1519  if ($type === 'close') {
1520  $XMLcontent .= '</' . $val['tag'] . '>';
1521  }
1522  // Cdata
1523  if ($type === 'cdata') {
1524  $XMLcontent .= htmlspecialchars($val['value']);
1525  }
1526  }
1527  return $XMLcontent;
1528  }
1529 
1530  /*************************
1531  *
1532  * FILES FUNCTIONS
1533  *
1534  *************************/
1542  public static function ‪getUrl(‪$url)
1543  {
1544  // Looks like it's an external file, use Guzzle by default
1545  if (preg_match('/^(?:http|ftp)s?|s(?:ftp|cp):/', ‪$url)) {
1546  $requestFactory = static::makeInstance(RequestFactory::class);
1547  try {
1548  $response = $requestFactory->request(‪$url);
1549  } catch (RequestException $exception) {
1550  return false;
1551  }
1552  $content = $response->getBody()->getContents();
1553  } else {
1554  $content = @file_get_contents(‪$url);
1555  }
1556  return $content;
1557  }
1558 
1567  public static function ‪writeFile($file, $content, $changePermissions = false)
1568  {
1569  if (!@is_file($file)) {
1570  $changePermissions = true;
1571  }
1572  if ($fd = fopen($file, 'wb')) {
1573  $res = fwrite($fd, $content);
1574  fclose($fd);
1575  if ($res === false) {
1576  return false;
1577  }
1578  // Change the permissions only if the file has just been created
1579  if ($changePermissions) {
1580  static::fixPermissions($file);
1581  }
1582  return true;
1583  }
1584  return false;
1585  }
1586 
1594  public static function ‪fixPermissions($path, $recursive = false)
1595  {
1596  $targetPermissions = null;
1597  if (‪Environment::isWindows()) {
1598  return true;
1599  }
1600  $result = false;
1601  // Make path absolute
1602  if (!‪PathUtility::isAbsolutePath($path)) {
1603  $path = static::getFileAbsFileName($path);
1604  }
1605  if (static::isAllowedAbsPath($path)) {
1606  if (@is_file($path)) {
1607  $targetPermissions = (string)(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['fileCreateMask'] ?? '0644');
1608  } elseif (@is_dir($path)) {
1609  $targetPermissions = (string)(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask'] ?? '0755');
1610  }
1611  if (!empty($targetPermissions)) {
1612  // make sure it's always 4 digits
1613  $targetPermissions = str_pad($targetPermissions, 4, '0', STR_PAD_LEFT);
1614  $targetPermissions = octdec($targetPermissions);
1615  // "@" is there because file is not necessarily OWNED by the user
1616  $result = @chmod($path, (int)$targetPermissions);
1617  }
1618  // Set createGroup if not empty
1619  if (
1620  isset(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['createGroup'])
1621  && ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['createGroup'] !== ''
1622  ) {
1623  // "@" is there because file is not necessarily OWNED by the user
1624  $changeGroupResult = @chgrp($path, ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['createGroup']);
1625  $result = $changeGroupResult ? $result : false;
1626  }
1627  // Call recursive if recursive flag if set and $path is directory
1628  if ($recursive && @is_dir($path)) {
1629  $handle = opendir($path);
1630  if (is_resource($handle)) {
1631  while (($file = readdir($handle)) !== false) {
1632  $recursionResult = null;
1633  if ($file !== '.' && $file !== '..') {
1634  if (@is_file($path . '/' . $file)) {
1635  $recursionResult = static::fixPermissions($path . '/' . $file);
1636  } elseif (@is_dir($path . '/' . $file)) {
1637  $recursionResult = static::fixPermissions($path . '/' . $file, true);
1638  }
1639  if (isset($recursionResult) && !$recursionResult) {
1640  $result = false;
1641  }
1642  }
1643  }
1644  closedir($handle);
1645  }
1646  }
1647  }
1648  return $result;
1649  }
1650 
1659  public static function ‪writeFileToTypo3tempDir($filepath, $content)
1660  {
1661  // Parse filepath into directory and basename:
1662  $fI = pathinfo($filepath);
1663  $fI['dirname'] .= '/';
1664  // Check parts:
1665  if (!static::validPathStr($filepath) || !$fI['basename'] || strlen($fI['basename']) >= 60) {
1666  return 'Input filepath "' . $filepath . '" was generally invalid!';
1667  }
1668 
1669  // Setting main temporary directory name (standard)
1670  $allowedPathPrefixes = [
1671  ‪Environment::getPublicPath() . '/typo3temp' => 'Environment::getPublicPath() + "/typo3temp/"',
1672  ];
1673  // Also allow project-path + /var/
1674  if (‪Environment::getVarPath() !== ‪Environment::getPublicPath() . '/typo3temp/var') {
1675  $relPath = substr(‪Environment::getVarPath(), strlen(‪Environment::getProjectPath()) + 1);
1676  $allowedPathPrefixes[‪Environment::getVarPath()] = 'ProjectPath + ' . $relPath;
1677  }
1678 
1679  $errorMessage = null;
1680  foreach ($allowedPathPrefixes as $pathPrefix => $prefixLabel) {
1681  $dirName = $pathPrefix . '/';
1682  // Invalid file path, let's check for the other path, if it exists
1683  if (!str_starts_with($fI['dirname'], $dirName)) {
1684  if ($errorMessage === null) {
1685  $errorMessage = '"' . $fI['dirname'] . '" was not within directory ' . $prefixLabel;
1686  }
1687  continue;
1688  }
1689  // This resets previous error messages from the first path
1690  $errorMessage = null;
1691 
1692  if (!@is_dir($dirName)) {
1693  $errorMessage = $prefixLabel . ' was not a directory!';
1694  // continue and see if the next iteration resets the errorMessage above
1695  continue;
1696  }
1697  // Checking if the "subdir" is found
1698  $subdir = substr($fI['dirname'], strlen($dirName));
1699  if ($subdir) {
1700  if (preg_match('#^(?:[[:alnum:]_]+/)+$#', $subdir)) {
1701  $dirName .= $subdir;
1702  if (!@is_dir($dirName)) {
1703  static::mkdir_deep($pathPrefix . '/' . $subdir);
1704  }
1705  } else {
1706  $errorMessage = 'Subdir, "' . $subdir . '", was NOT on the form "[[:alnum:]_]/+"';
1707  break;
1708  }
1709  }
1710  // Checking dir-name again (sub-dir might have been created)
1711  if (@is_dir($dirName)) {
1712  if ($filepath === $dirName . $fI['basename']) {
1713  static::writeFile($filepath, $content);
1714  if (!@is_file($filepath)) {
1715  $errorMessage = 'The file was not written to the disk. Please, check that you have write permissions to the ' . $prefixLabel . ' directory.';
1716  }
1717  break;
1718  }
1719  $errorMessage = 'Calculated file location didn\'t match input "' . $filepath . '".';
1720  break;
1721  }
1722  $errorMessage = '"' . $dirName . '" is not a directory!';
1723  break;
1724  }
1725  return $errorMessage;
1726  }
1727 
1736  public static function ‪mkdir($newFolder)
1737  {
1738  $result = @‪mkdir($newFolder, (int)octdec((string)(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask'] ?? '0')));
1739  if ($result) {
1740  static::fixPermissions($newFolder);
1741  }
1742  return $result;
1743  }
1744 
1753  public static function ‪mkdir_deep($directory)
1754  {
1755  if (!is_string($directory)) {
1756  throw new \InvalidArgumentException('The specified directory is of type "' . gettype($directory) . '" but a string is expected.', 1303662955);
1757  }
1758  // Ensure there is only one slash
1759  $fullPath = rtrim($directory, '/') . '/';
1760  if ($fullPath !== '/' && !is_dir($fullPath)) {
1761  $firstCreatedPath = static::createDirectoryPath($fullPath);
1762  if ($firstCreatedPath !== '') {
1763  static::fixPermissions($firstCreatedPath, true);
1764  }
1765  }
1766  }
1767 
1779  protected static function ‪createDirectoryPath($fullDirectoryPath)
1780  {
1781  $currentPath = $fullDirectoryPath;
1782  $firstCreatedPath = '';
1783  $permissionMask = (int)octdec((string)(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask'] ?? '0'));
1784  if (!@is_dir($currentPath)) {
1785  do {
1786  $firstCreatedPath = $currentPath;
1787  $separatorPosition = (int)strrpos($currentPath, DIRECTORY_SEPARATOR);
1788  $currentPath = substr($currentPath, 0, $separatorPosition);
1789  } while (!is_dir($currentPath) && $separatorPosition > 0);
1790  $result = @‪mkdir($fullDirectoryPath, $permissionMask, true);
1791  // Check existence of directory again to avoid race condition. Directory could have get created by another process between previous is_dir() and mkdir()
1792  if (!$result && !@is_dir($fullDirectoryPath)) {
1793  throw new \RuntimeException('Could not create directory "' . $fullDirectoryPath . '"!', 1170251401);
1794  }
1795  }
1796  return $firstCreatedPath;
1797  }
1798 
1806  public static function ‪rmdir($path, $removeNonEmpty = false)
1807  {
1808  $OK = false;
1809  // Remove trailing slash
1810  $path = preg_replace('|/$|', '', $path) ?? '';
1811  $isWindows = DIRECTORY_SEPARATOR === '\\';
1812  if (file_exists($path)) {
1813  $OK = true;
1814  if (!is_link($path) && is_dir($path)) {
1815  if ($removeNonEmpty === true && ($handle = @opendir($path))) {
1816  $entries = [];
1817 
1818  while (false !== ($file = readdir($handle))) {
1819  if ($file === '.' || $file === '..') {
1820  continue;
1821  }
1822 
1823  $entries[] = $path . '/' . $file;
1824  }
1825 
1826  closedir($handle);
1827 
1828  foreach ($entries as $entry) {
1829  if (!static::rmdir($entry, $removeNonEmpty)) {
1830  $OK = false;
1831  }
1832  }
1833  }
1834  if ($OK) {
1835  $OK = @‪rmdir($path);
1836  }
1837  } elseif (is_link($path) && is_dir($path) && $isWindows) {
1838  $OK = @‪rmdir($path);
1839  } else {
1840  // If $path is a file, simply remove it
1841  $OK = @unlink($path);
1842  }
1843  clearstatcache();
1844  } elseif (is_link($path)) {
1845  $OK = @unlink($path);
1846  if (!$OK && $isWindows) {
1847  // Try to delete dead folder links on Windows systems
1848  $OK = @‪rmdir($path);
1849  }
1850  clearstatcache();
1851  }
1852  return $OK;
1853  }
1854 
1863  public static function ‪get_dirs($path)
1864  {
1865  $dirs = null;
1866  if ($path) {
1867  if (is_dir($path)) {
1868  ‪$dir = scandir($path);
1869  $dirs = [];
1870  foreach (‪$dir as $entry) {
1871  if (is_dir($path . '/' . $entry) && $entry !== '..' && $entry !== '.') {
1872  $dirs[] = $entry;
1873  }
1874  }
1875  } else {
1876  $dirs = 'error';
1877  }
1878  }
1879  return $dirs;
1880  }
1881 
1894  public static function getFilesInDir($path, $extensionList = '', $prependPath = false, $order = '', $excludePattern = '')
1895  {
1896  $excludePattern = (string)$excludePattern;
1897  $path = rtrim($path, '/');
1898  if (!@is_dir($path)) {
1899  return [];
1900  }
1901 
1902  $rawFileList = scandir($path);
1903  if ($rawFileList === false) {
1904  return 'error opening path: "' . $path . '"';
1905  }
1906 
1907  $pathPrefix = $path . '/';
1908  $allowedFileExtensionArray = ‪self::trimExplode(',', $extensionList);
1909  $extensionList = ',' . str_replace(' ', '', $extensionList) . ',';
1910  $files = [];
1911  foreach ($rawFileList as $entry) {
1912  $completePathToEntry = $pathPrefix . $entry;
1913  if (!@is_file($completePathToEntry)) {
1914  continue;
1915  }
1916 
1917  foreach ($allowedFileExtensionArray as $allowedFileExtension) {
1918  if (
1919  ($extensionList === ',,' || stripos($extensionList, ',' . substr($entry, strlen($allowedFileExtension) * -1, strlen($allowedFileExtension)) . ',') !== false)
1920  && ($excludePattern === '' || !preg_match('/^' . $excludePattern . '$/', $entry))
1921  ) {
1922  if ($order !== 'mtime') {
1923  $files[] = $entry;
1924  } else {
1925  // Store the value in the key so we can do a fast asort later.
1926  $files[$entry] = filemtime($completePathToEntry);
1927  }
1928  }
1929  }
1930  }
1931 
1932  $valueName = 'value';
1933  if ($order === 'mtime') {
1934  asort($files);
1935  $valueName = 'key';
1936  }
1937 
1938  $valuePathPrefix = $prependPath ? $pathPrefix : '';
1939  $foundFiles = [];
1940  foreach ($files as $key => $value) {
1941  // Don't change this ever - extensions may depend on the fact that the hash is an md5 of the path! (import/export extension)
1942  $foundFiles[md5($pathPrefix . ${$valueName})] = $valuePathPrefix . ${$valueName};
1943  }
1944 
1945  return $foundFiles;
1946  }
1947 
1959  public static function getAllFilesAndFoldersInPath(array $fileArr, $path, $extList = '', $regDirs = false, $recursivityLevels = 99, $excludePattern = '')
1960  {
1961  if ($regDirs) {
1962  $fileArr[md5($path)] = $path;
1963  }
1964  $fileArr = array_merge($fileArr, (array)self::getFilesInDir($path, $extList, true, '', $excludePattern));
1965  $dirs = ‪self::get_dirs($path);
1966  if ($recursivityLevels > 0 && is_array($dirs)) {
1967  foreach ($dirs as $subdirs) {
1968  if ((string)$subdirs !== '' && ($excludePattern === '' || !preg_match('/^' . $excludePattern . '$/', $subdirs))) {
1969  $fileArr = self::getAllFilesAndFoldersInPath($fileArr, $path . $subdirs . '/', $extList, $regDirs, $recursivityLevels - 1, $excludePattern);
1970  }
1971  }
1972  }
1973  return $fileArr;
1974  }
1975 
1983  public static function removePrefixPathFromList(array $fileArr, string $prefixToRemove)
1984  {
1985  foreach ($fileArr as &$absFileRef) {
1986  if (str_starts_with($absFileRef, $prefixToRemove)) {
1987  $absFileRef = substr($absFileRef, strlen($prefixToRemove));
1988  } else {
1989  return 'ERROR: One or more of the files was NOT prefixed with the prefix-path!';
1990  }
1991  }
1992  unset($absFileRef);
1993  return $fileArr;
1994  }
1995 
2002  public static function fixWindowsFilePath($theFile)
2003  {
2004  return str_replace(['\\', '//'], '/', $theFile);
2005  }
2006 
2014  public static function resolveBackPath($pathStr)
2015  {
2016  if (!str_contains($pathStr, '..')) {
2017  return $pathStr;
2018  }
2019  $parts = explode('/', $pathStr);
2020  ‪$output = [];
2021  $c = 0;
2022  foreach ($parts as $part) {
2023  if ($part === '..') {
2024  if ($c) {
2025  array_pop(‪$output);
2026  --$c;
2027  } else {
2028  ‪$output[] = $part;
2029  }
2030  } else {
2031  ++$c;
2032  ‪$output[] = $part;
2033  }
2034  }
2035  return implode('/', ‪$output);
2036  }
2037 
2047  public static function locationHeaderUrl($path)
2048  {
2049  if (str_starts_with($path, '//')) {
2050  return $path;
2051  }
2052 
2053  // relative to HOST
2054  if (str_starts_with($path, '/')) {
2055  return self::getIndpEnv('TYPO3_REQUEST_HOST') . $path;
2056  }
2057 
2058  $urlComponents = parse_url($path);
2059  if (!($urlComponents['scheme'] ?? false)) {
2060  // No scheme either
2061  return self::getIndpEnv('TYPO3_REQUEST_DIR') . $path;
2062  }
2063 
2064  return $path;
2065  }
2066 
2074  public static function getMaxUploadFileSize()
2075  {
2076  $uploadMaxFilesize = (string)ini_get('upload_max_filesize');
2077  $postMaxSize = (string)ini_get('post_max_size');
2078  // Check for PHP restrictions of the maximum size of one of the $_FILES
2079  $phpUploadLimit = self::getBytesFromSizeMeasurement($uploadMaxFilesize);
2080  // Check for PHP restrictions of the maximum $_POST size
2081  $phpPostLimit = self::getBytesFromSizeMeasurement($postMaxSize);
2082  // If the total amount of post data is smaller (!) than the upload_max_filesize directive,
2083  // then this is the real limit in PHP
2084  $phpUploadLimit = $phpPostLimit > 0 && $phpPostLimit < $phpUploadLimit ? $phpPostLimit : $phpUploadLimit;
2085  return floor($phpUploadLimit) / 1024;
2086  }
2087 
2094  public static function getBytesFromSizeMeasurement($measurement)
2095  {
2096  $bytes = (float)$measurement;
2097  if (stripos($measurement, 'G')) {
2098  $bytes *= 1024 * 1024 * 1024;
2099  } elseif (stripos($measurement, 'M')) {
2100  $bytes *= 1024 * 1024;
2101  } elseif (stripos($measurement, 'K')) {
2102  $bytes *= 1024;
2103  }
2104  return (int)$bytes;
2105  }
2106 
2123  public static function createVersionNumberedFilename($file): string
2124  {
2125  $isFrontend = (‪$GLOBALS['TYPO3_REQUEST'] ?? null) instanceof ServerRequestInterface
2126  && ‪ApplicationType::fromRequest(‪$GLOBALS['TYPO3_REQUEST'])->isFrontend();
2127  $lookupFile = explode('?', $file);
2128  $path = $lookupFile[0];
2129 
2130  // @todo: in v13 this should be resolved by using Environment::getPublicPath() only
2131  if ($isFrontend) {
2132  // Frontend should still allow /static/myfile.css - see #98106
2133  // This should happen regardless of the incoming path is absolute or not
2134  $path = self::resolveBackPath(self::dirname(‪Environment::getCurrentScript()) . '/' . $path);
2135  } elseif (!‪PathUtility::isAbsolutePath($path)) {
2136  // Backend and non-absolute path
2137  $path = self::resolveBackPath(self::dirname(‪Environment::getCurrentScript()) . '/' . $path);
2138  }
2139 
2140  if ($isFrontend) {
2141  $configValue = ‪$GLOBALS['TYPO3_CONF_VARS']['FE']['versionNumberInFilename'] ?? false;
2142  // Fallback layer for TYPO3 v12 - @deprecated can be removed in TYPO3 v13.0.
2143  if ($configValue === 'embed') {
2144  // no @deprecated warning here, as this might fill a lot of log files.
2145  $configValue = true;
2146  } else {
2147  $configValue = $configValue === true;
2148  }
2149  } else {
2150  $configValue = (bool)(‪$GLOBALS['TYPO3_CONF_VARS']['BE']['versionNumberInFilename'] ?? false);
2151  }
2152  try {
2153  $fileExists = file_exists($path);
2154  } catch (\Throwable $e) {
2155  $fileExists = false;
2156  }
2157  if (!$fileExists) {
2158  // File not found, return filename unaltered
2159  $fullName = $file;
2160  } else {
2161  if (!$configValue) {
2162  // If .htaccess rule is not configured,
2163  // use the default query-string method
2164  if (!empty($lookupFile[1])) {
2165  $separator = '&';
2166  } else {
2167  $separator = '?';
2168  }
2169  $fullName = $file . $separator . filemtime($path);
2170  } else {
2171  // Change the filename
2172  $name = explode('.', $lookupFile[0]);
2173  $extension = array_pop($name);
2174  array_push($name, filemtime($path), $extension);
2175  $fullName = implode('.', $name);
2176  // Append potential query string
2177  $fullName .= !empty($lookupFile[1]) ? '?' . $lookupFile[1] : '';
2178  }
2179  }
2180  return $fullName;
2181  }
2182 
2190  public static function writeJavaScriptContentToTemporaryFile(string $content)
2191  {
2192  $script = 'typo3temp/assets/js/' . md5($content) . '.js';
2193  if (!@is_file(‪Environment::getPublicPath() . '/' . $script)) {
2195  }
2196  return $script;
2197  }
2198 
2206  public static function writeStyleSheetContentToTemporaryFile(string $content)
2207  {
2208  $script = 'typo3temp/assets/css/' . md5($content) . '.css';
2209  if (!@is_file(‪Environment::getPublicPath() . '/' . $script)) {
2211  }
2212  return $script;
2213  }
2214 
2215  /*************************
2216  *
2217  * SYSTEM INFORMATION
2218  *
2219  *************************/
2220 
2229  public static function linkThisScript(array $getParams = [])
2230  {
2231  $parts = self::getIndpEnv('SCRIPT_NAME');
2232  $params = ‪self::_GET();
2233  foreach ($getParams as $key => $value) {
2234  if ($value !== '') {
2235  $params[$key] = $value;
2236  } else {
2237  unset($params[$key]);
2238  }
2239  }
2240  $pString = ‪self::implodeArrayForUrl('', $params);
2241  return $pString ? $parts . '?' . ltrim($pString, '&') : $parts;
2242  }
2243 
2251  public static function setIndpEnv($envName, $value)
2252  {
2253  self::$indpEnvCache[$envName] = $value;
2254  }
2255 
2264  public static function getIndpEnv($getEnvName)
2265  {
2266  if (array_key_exists($getEnvName, self::$indpEnvCache)) {
2267  return self::$indpEnvCache[$getEnvName];
2268  }
2269 
2270  /*
2271  Conventions:
2272  output from parse_url():
2273  URL: http://username:password@192.168.1.4:8080/typo3/32/temp/phpcheck/index.php/arg1/arg2/arg3/?arg1,arg2,arg3&p1=parameter1&p2[key]=value#link1
2274  [scheme] => 'http'
2275  [user] => 'username'
2276  [pass] => 'password'
2277  [host] => '192.168.1.4'
2278  [port] => '8080'
2279  [path] => '/typo3/32/temp/phpcheck/index.php/arg1/arg2/arg3/'
2280  [query] => 'arg1,arg2,arg3&p1=parameter1&p2[key]=value'
2281  [fragment] => 'link1'Further definition: [path_script] = '/typo3/32/temp/phpcheck/index.php'
2282  [path_dir] = '/typo3/32/temp/phpcheck/'
2283  [path_info] = '/arg1/arg2/arg3/'
2284  [path] = [path_script/path_dir][path_info]Keys supported:URI______:
2285  REQUEST_URI = [path]?[query] = /typo3/32/temp/phpcheck/index.php/arg1/arg2/arg3/?arg1,arg2,arg3&p1=parameter1&p2[key]=value
2286  HTTP_HOST = [host][:[port]] = 192.168.1.4:8080
2287  SCRIPT_NAME = [path_script]++ = /typo3/32/temp/phpcheck/index.php // NOTICE THAT SCRIPT_NAME will return the php-script name ALSO. [path_script] may not do that (eg. '/somedir/' may result in SCRIPT_NAME '/somedir/index.php')!
2288  PATH_INFO = [path_info] = /arg1/arg2/arg3/
2289  QUERY_STRING = [query] = arg1,arg2,arg3&p1=parameter1&p2[key]=value
2290  HTTP_REFERER = [scheme]://[host][:[port]][path] = http://192.168.1.4:8080/typo3/32/temp/phpcheck/index.php/arg1/arg2/arg3/?arg1,arg2,arg3&p1=parameter1&p2[key]=value
2291  (Notice: NO username/password + NO fragment)CLIENT____:
2292  REMOTE_ADDR = (client IP)
2293  REMOTE_HOST = (client host)
2294  HTTP_USER_AGENT = (client user agent)
2295  HTTP_ACCEPT_LANGUAGE = (client accept language)SERVER____:
2296  SCRIPT_FILENAME = Absolute filename of script (Differs between windows/unix). On windows 'C:\\some\\path\\' will be converted to 'C:/some/path/'Special extras:
2297  TYPO3_HOST_ONLY = [host] = 192.168.1.4
2298  TYPO3_PORT = [port] = 8080 (blank if 80, taken from host value)
2299  TYPO3_REQUEST_HOST = [scheme]://[host][:[port]]
2300  TYPO3_REQUEST_URL = [scheme]://[host][:[port]][path]?[query] (scheme will by default be "http" until we can detect something different)
2301  TYPO3_REQUEST_SCRIPT = [scheme]://[host][:[port]][path_script]
2302  TYPO3_REQUEST_DIR = [scheme]://[host][:[port]][path_dir]
2303  TYPO3_SITE_URL = [scheme]://[host][:[port]][path_dir] of the TYPO3 website frontend
2304  TYPO3_SITE_PATH = [path_dir] of the TYPO3 website frontend
2305  TYPO3_SITE_SCRIPT = [script / Speaking URL] of the TYPO3 website
2306  TYPO3_DOCUMENT_ROOT = Absolute path of root of documents: TYPO3_DOCUMENT_ROOT.SCRIPT_NAME = SCRIPT_FILENAME (typically)
2307  TYPO3_SSL = Returns TRUE if this session uses SSL/TLS (https)
2308  TYPO3_PROXY = Returns TRUE if this session runs over a well known proxyNotice: [fragment] is apparently NEVER available to the script!Testing suggestions:
2309  - Output all the values.
2310  - In the script, make a link to the script it self, maybe add some parameters and click the link a few times so HTTP_REFERER is seen
2311  - ALSO TRY the script from the ROOT of a site (like 'http://www.mytest.com/' and not 'http://www.mytest.com/test/' !!)
2312  */
2313  $retVal = '';
2314  switch ((string)$getEnvName) {
2315  case 'SCRIPT_NAME':
2316  $retVal = $_SERVER['SCRIPT_NAME'] ?? '';
2317  // Add a prefix if TYPO3 is behind a proxy: ext-domain.com => int-server.com/prefix
2318  if (self::cmpIP($_SERVER['REMOTE_ADDR'] ?? '', ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyIP'] ?? '')) {
2319  if (self::getIndpEnv('TYPO3_SSL') && ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyPrefixSSL']) {
2320  $retVal = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyPrefixSSL'] . $retVal;
2321  } elseif (‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyPrefix']) {
2322  $retVal = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyPrefix'] . $retVal;
2323  }
2324  }
2325  $retVal = self::encodeFileSystemPathComponentForUrlPath($retVal);
2326  break;
2327  case 'SCRIPT_FILENAME':
2329  break;
2330  case 'REQUEST_URI':
2331  // Typical application of REQUEST_URI is return urls, forms submitting to itself etc. Example: returnUrl='.rawurlencode(\TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('REQUEST_URI'))
2332  if (!empty(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['requestURIvar'])) {
2333  // This is for URL rewriters that store the original URI in a server variable (eg ISAPI_Rewriter for IIS: HTTP_X_REWRITE_URL)
2334  [$v, $n] = explode('|', ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['requestURIvar']);
2335  $retVal = ‪$GLOBALS[$v][$n];
2336  } elseif (empty($_SERVER['REQUEST_URI'])) {
2337  // This is for ISS/CGI which does not have the REQUEST_URI available.
2338  $retVal = '/' . ltrim(self::getIndpEnv('SCRIPT_NAME'), '/') . (!empty($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : '');
2339  } else {
2340  $retVal = '/' . ltrim($_SERVER['REQUEST_URI'], '/');
2341  }
2342  // Add a prefix if TYPO3 is behind a proxy: ext-domain.com => int-server.com/prefix
2343  if (isset($_SERVER['REMOTE_ADDR'], ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyIP'])
2344  && self::cmpIP($_SERVER['REMOTE_ADDR'], ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyIP'])
2345  ) {
2346  if (self::getIndpEnv('TYPO3_SSL') && ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyPrefixSSL']) {
2347  $retVal = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyPrefixSSL'] . $retVal;
2348  } elseif (‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyPrefix']) {
2349  $retVal = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyPrefix'] . $retVal;
2350  }
2351  }
2352  break;
2353  case 'PATH_INFO':
2354  $retVal = $_SERVER['PATH_INFO'] ?? '';
2355  break;
2356  case 'TYPO3_REV_PROXY':
2357  $retVal = ‪self::cmpIP($_SERVER['REMOTE_ADDR'] ?? '', ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyIP']);
2358  break;
2359  case 'REMOTE_ADDR':
2360  $retVal = $_SERVER['REMOTE_ADDR'] ?? '';
2361  if (self::cmpIP($retVal, ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyIP'] ?? '')) {
2362  $ip = ‪self::trimExplode(',', $_SERVER['HTTP_X_FORWARDED_FOR'] ?? '');
2363  // Choose which IP in list to use
2364  if (!empty($ip)) {
2365  switch (‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyHeaderMultiValue']) {
2366  case 'last':
2367  $ip = array_pop($ip);
2368  break;
2369  case 'first':
2370  $ip = array_shift($ip);
2371  break;
2372  case 'none':
2373 
2374  default:
2375  $ip = '';
2376  }
2377  }
2378  if (self::validIP((string)$ip)) {
2379  $retVal = $ip;
2380  }
2381  }
2382  break;
2383  case 'HTTP_HOST':
2384  // if it is not set we're most likely on the cli
2385  $retVal = $_SERVER['HTTP_HOST'] ?? '';
2386  if (isset($_SERVER['REMOTE_ADDR']) && static::cmpIP($_SERVER['REMOTE_ADDR'], ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyIP'])) {
2387  $host = ‪self::trimExplode(',', $_SERVER['HTTP_X_FORWARDED_HOST'] ?? '');
2388  // Choose which host in list to use
2389  if (!empty($host)) {
2390  switch (‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyHeaderMultiValue']) {
2391  case 'last':
2392  $host = array_pop($host);
2393  break;
2394  case 'first':
2395  $host = array_shift($host);
2396  break;
2397  case 'none':
2398 
2399  default:
2400  $host = '';
2401  }
2402  }
2403  if ($host) {
2404  $retVal = $host;
2405  }
2406  }
2407  break;
2408  case 'HTTP_REFERER':
2409 
2410  case 'HTTP_USER_AGENT':
2411 
2412  case 'HTTP_ACCEPT_ENCODING':
2413 
2414  case 'HTTP_ACCEPT_LANGUAGE':
2415 
2416  case 'REMOTE_HOST':
2417 
2418  case 'QUERY_STRING':
2419  $retVal = $_SERVER[$getEnvName] ?? '';
2420  break;
2421  case 'TYPO3_DOCUMENT_ROOT':
2422  // Get the web root (it is not the root of the TYPO3 installation)
2423  // The absolute path of the script can be calculated with TYPO3_DOCUMENT_ROOT + SCRIPT_FILENAME
2424  // Some CGI-versions (LA13CGI) and mod-rewrite rules on MODULE versions will deliver a 'wrong' DOCUMENT_ROOT (according to our description). Further various aliases/mod_rewrite rules can disturb this as well.
2425  // Therefore the DOCUMENT_ROOT is now always calculated as the SCRIPT_FILENAME minus the end part shared with SCRIPT_NAME.
2426  $SFN = self::getIndpEnv('SCRIPT_FILENAME');
2427  // Use rawurldecode to reverse the result of self::encodeFileSystemPathComponentForUrlPath()
2428  // which has been applied to getIndpEnv(SCRIPT_NAME) for web URI usage.
2429  // We compare with a file system path (SCRIPT_FILENAME) in here and therefore need to undo the encoding.
2430  $SN_A = array_map(rawurldecode(...), explode('/', strrev(self::getIndpEnv('SCRIPT_NAME'))));
2431  $SFN_A = explode('/', strrev($SFN));
2432  $acc = [];
2433  foreach ($SN_A as $kk => $vv) {
2434  if ((string)$SFN_A[$kk] === (string)$vv) {
2435  $acc[] = $vv;
2436  } else {
2437  break;
2438  }
2439  }
2440  $commonEnd = strrev(implode('/', $acc));
2441  if ((string)$commonEnd !== '') {
2442  $retVal = substr($SFN, 0, -(strlen($commonEnd) + 1));
2443  }
2444  break;
2445  case 'TYPO3_HOST_ONLY':
2446  $httpHost = self::getIndpEnv('HTTP_HOST');
2447  $httpHostBracketPosition = strpos($httpHost, ']');
2448  $httpHostParts = explode(':', $httpHost);
2449  $retVal = $httpHostBracketPosition !== false ? substr($httpHost, 0, $httpHostBracketPosition + 1) : array_shift($httpHostParts);
2450  break;
2451  case 'TYPO3_PORT':
2452  $httpHost = self::getIndpEnv('HTTP_HOST');
2453  $httpHostOnly = self::getIndpEnv('TYPO3_HOST_ONLY');
2454  $retVal = strlen($httpHost) > strlen($httpHostOnly) ? substr($httpHost, strlen($httpHostOnly) + 1) : '';
2455  break;
2456  case 'TYPO3_REQUEST_HOST':
2457  $retVal = (self::getIndpEnv('TYPO3_SSL') ? 'https://' : 'http://') . self::getIndpEnv('HTTP_HOST');
2458  break;
2459  case 'TYPO3_REQUEST_URL':
2460  $retVal = self::getIndpEnv('TYPO3_REQUEST_HOST') . self::getIndpEnv('REQUEST_URI');
2461  break;
2462  case 'TYPO3_REQUEST_SCRIPT':
2463  $retVal = self::getIndpEnv('TYPO3_REQUEST_HOST') . self::getIndpEnv('SCRIPT_NAME');
2464  break;
2465  case 'TYPO3_REQUEST_DIR':
2466  $retVal = self::getIndpEnv('TYPO3_REQUEST_HOST') . self::dirname(self::getIndpEnv('SCRIPT_NAME')) . '/';
2467  break;
2468  case 'TYPO3_SITE_URL':
2471  ‪$url = self::getIndpEnv('TYPO3_REQUEST_DIR');
2472  $siteUrl = substr(‪$url, 0, -strlen($lPath));
2473  if (substr($siteUrl, -1) !== '/') {
2474  $siteUrl .= '/';
2475  }
2476  $retVal = $siteUrl;
2477  }
2478  break;
2479  case 'TYPO3_SITE_PATH':
2480  $retVal = substr(self::getIndpEnv('TYPO3_SITE_URL'), strlen(self::getIndpEnv('TYPO3_REQUEST_HOST')));
2481  break;
2482  case 'TYPO3_SITE_SCRIPT':
2483  $retVal = substr(self::getIndpEnv('TYPO3_REQUEST_URL'), strlen(self::getIndpEnv('TYPO3_SITE_URL')));
2484  break;
2485  case 'TYPO3_SSL':
2486  $proxySSL = trim(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxySSL'] ?? '');
2487  if ($proxySSL === '*') {
2488  $proxySSL = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyIP'];
2489  }
2490  if (self::cmpIP($_SERVER['REMOTE_ADDR'] ?? '', $proxySSL)) {
2491  $retVal = true;
2492  } else {
2493  $retVal = self::webserverUsesHttps();
2494  }
2495  break;
2496  case '_ARRAY':
2497  $out = [];
2498  // Here, list ALL possible keys to this function for debug display.
2499  $envTestVars = [
2500  'HTTP_HOST',
2501  'TYPO3_HOST_ONLY',
2502  'TYPO3_PORT',
2503  'PATH_INFO',
2504  'QUERY_STRING',
2505  'REQUEST_URI',
2506  'HTTP_REFERER',
2507  'TYPO3_REQUEST_HOST',
2508  'TYPO3_REQUEST_URL',
2509  'TYPO3_REQUEST_SCRIPT',
2510  'TYPO3_REQUEST_DIR',
2511  'TYPO3_SITE_URL',
2512  'TYPO3_SITE_SCRIPT',
2513  'TYPO3_SSL',
2514  'TYPO3_REV_PROXY',
2515  'SCRIPT_NAME',
2516  'TYPO3_DOCUMENT_ROOT',
2517  'SCRIPT_FILENAME',
2518  'REMOTE_ADDR',
2519  'REMOTE_HOST',
2520  'HTTP_USER_AGENT',
2521  'HTTP_ACCEPT_LANGUAGE',
2522  ];
2523  foreach ($envTestVars as $v) {
2524  $out[$v] = self::getIndpEnv($v);
2525  }
2526  reset($out);
2527  $retVal = $out;
2528  break;
2529  }
2530  self::$indpEnvCache[$getEnvName] = $retVal;
2531  return $retVal;
2532  }
2533 
2544  protected static function webserverUsesHttps()
2545  {
2546  if (!empty($_SERVER['SSL_SESSION_ID'])) {
2547  return true;
2548  }
2549 
2550  // https://secure.php.net/manual/en/reserved.variables.server.php
2551  // "Set to a non-empty value if the script was queried through the HTTPS protocol."
2552  return !empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off';
2553  }
2554 
2555  protected static function encodeFileSystemPathComponentForUrlPath(string $path): string
2556  {
2557  return implode('/', array_map(rawurlencode(...), explode('/', $path)));
2558  }
2559 
2560  /*************************
2561  *
2562  * TYPO3 SPECIFIC FUNCTIONS
2563  *
2564  *************************/
2574  public static function getFileAbsFileName($filename)
2575  {
2576  $fileName = (string)$filename;
2577  if ($fileName === '') {
2578  return '';
2579  }
2580  $checkForBackPath = fn (string $fileName): string => $fileName !== '' && static::validPathStr($fileName) ? $fileName : '';
2581 
2582  // Extension "EXT:" path resolving.
2583  if (‪PathUtility::isExtensionPath($fileName)) {
2584  try {
2586  } catch (‪PackageException) {
2587  $fileName = '';
2588  }
2589  return $checkForBackPath($fileName);
2590  }
2591 
2592  // Absolute path, but set to blank if not inside allowed directories.
2593  if (‪PathUtility::isAbsolutePath($fileName)) {
2594  if (str_starts_with($fileName, ‪Environment::getProjectPath()) ||
2595  str_starts_with($fileName, ‪Environment::getPublicPath())) {
2596  return $checkForBackPath($fileName);
2597  }
2598  return '';
2599  }
2600 
2601  // Relative path. Prepend with the public web folder.
2602  $fileName = ‪Environment::getPublicPath() . '/' . $fileName;
2603  return $checkForBackPath($fileName);
2604  }
2605 
2617  public static function validPathStr($theFile)
2618  {
2619  return !str_contains($theFile, '//') && !str_contains($theFile, '\\')
2620  && preg_match('#(?:^\\.\\.|/\\.\\./|[[:cntrl:]])#u', $theFile) === 0;
2621  }
2622 
2629  public static function isAllowedAbsPath($path)
2630  {
2631  if (substr($path, 0, 6) === 'vfs://') {
2632  return true;
2633  }
2634  $lockRootPath = ‪$GLOBALS['TYPO3_CONF_VARS']['BE']['lockRootPath'] ?? '';
2635  return ‪PathUtility::isAbsolutePath($path) && static::validPathStr($path)
2636  && (
2637  str_starts_with($path, ‪Environment::getProjectPath())
2638  || str_starts_with($path, ‪Environment::getPublicPath())
2639  || ($lockRootPath && str_starts_with($path, $lockRootPath))
2640  );
2641  }
2642 
2649  public static function copyDirectory($source, $destination)
2650  {
2651  if (!str_contains($source, ‪Environment::getProjectPath() . '/')) {
2652  $source = ‪Environment::getPublicPath() . '/' . $source;
2653  }
2654  if (!str_contains($destination, ‪Environment::getProjectPath() . '/')) {
2655  $destination = ‪Environment::getPublicPath() . '/' . $destination;
2656  }
2657  if (static::isAllowedAbsPath($source) && static::isAllowedAbsPath($destination)) {
2658  static::mkdir_deep($destination);
2659  $iterator = new \RecursiveIteratorIterator(
2660  new \RecursiveDirectoryIterator($source, \RecursiveDirectoryIterator::SKIP_DOTS),
2661  \RecursiveIteratorIterator::SELF_FIRST
2662  );
2664  foreach ($iterator as $item) {
2665  $target = $destination . '/' . static::fixWindowsFilePath($iterator->getSubPathName());
2666  if ($item->isDir()) {
2667  static::mkdir($target);
2668  } else {
2669  static::upload_copy_move(static::fixWindowsFilePath($item->getPathname()), $target);
2670  }
2671  }
2672  }
2673  }
2674 
2685  public static function sanitizeLocalUrl(‪$url = '')
2686  {
2687  $sanitizedUrl = '';
2688  if (!empty(‪$url)) {
2689  $decodedUrl = rawurldecode(‪$url);
2690  $parsedUrl = parse_url($decodedUrl);
2691  $testAbsoluteUrl = self::resolveBackPath($decodedUrl);
2692  $testRelativeUrl = self::resolveBackPath(self::dirname(self::getIndpEnv('SCRIPT_NAME')) . '/' . $decodedUrl);
2693  // Pass if URL is on the current host:
2694  if (self::isValidUrl($decodedUrl)) {
2695  if (self::isOnCurrentHost($decodedUrl) && str_starts_with($decodedUrl, self::getIndpEnv('TYPO3_SITE_URL'))) {
2696  $sanitizedUrl = ‪$url;
2697  }
2698  } elseif (‪PathUtility::isAbsolutePath($decodedUrl) && self::isAllowedAbsPath($decodedUrl)) {
2699  $sanitizedUrl = ‪$url;
2700  } elseif (str_starts_with($testAbsoluteUrl, self::getIndpEnv('TYPO3_SITE_PATH')) && $decodedUrl[0] === '/' &&
2701  substr($decodedUrl, 0, 2) !== '//'
2702  ) {
2703  $sanitizedUrl = ‪$url;
2704  } elseif (empty($parsedUrl['scheme']) && str_starts_with($testRelativeUrl, self::getIndpEnv('TYPO3_SITE_PATH'))
2705  && $decodedUrl[0] !== '/' && strpbrk($decodedUrl, '*:|"<>') === false && !str_contains($decodedUrl, '\\\\')
2706  ) {
2707  $sanitizedUrl = ‪$url;
2708  }
2709  }
2710  if (!empty(‪$url) && empty($sanitizedUrl)) {
2711  static::getLogger()->notice('The URL "{url}" is not considered to be local and was denied.', ['url' => ‪$url]);
2712  }
2713  return $sanitizedUrl;
2714  }
2715 
2724  public static function upload_copy_move($source, $destination)
2725  {
2726  if (is_array(‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][\‪TYPO3\CMS\Core\Utility\GeneralUtility::class]['moveUploadedFile'] ?? null)) {
2727  $params = ['source' => $source, 'destination' => $destination, 'method' => 'upload_copy_move'];
2728  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][\‪TYPO3\CMS\Core\Utility\GeneralUtility::class]['moveUploadedFile'] as $hookMethod) {
2729  $fakeThis = null;
2730  self::callUserFunction($hookMethod, $params, $fakeThis);
2731  }
2732  }
2733 
2734  $result = false;
2735  if (is_uploaded_file($source)) {
2736  // Return the value of move_uploaded_file, and if FALSE the temporary $source is still
2737  // around so the user can use unlink to delete it:
2738  $result = move_uploaded_file($source, $destination);
2739  } else {
2740  @copy($source, $destination);
2741  }
2742  // Change the permissions of the file
2743  ‪self::fixPermissions($destination);
2744  // If here the file is copied and the temporary $source is still around,
2745  // so when returning FALSE the user can try unlink to delete the $source
2746  return $result;
2747  }
2748 
2759  public static function upload_to_tempfile($uploadedFileName)
2760  {
2761  if (is_uploaded_file($uploadedFileName)) {
2762  $tempFile = self::tempnam('upload_temp_');
2763  if (is_array(‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][\‪TYPO3\CMS\Core\Utility\GeneralUtility::class]['moveUploadedFile'] ?? null)) {
2764  $params = ['source' => $uploadedFileName, 'destination' => $tempFile, 'method' => 'upload_to_tempfile'];
2765  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][\‪TYPO3\CMS\Core\Utility\GeneralUtility::class]['moveUploadedFile'] as $hookMethod) {
2766  $fakeThis = null;
2767  self::callUserFunction($hookMethod, $params, $fakeThis);
2768  }
2769  }
2770 
2771  move_uploaded_file($uploadedFileName, $tempFile);
2772  return @is_file($tempFile) ? $tempFile : '';
2773  }
2774 
2775  return '';
2776  }
2777 
2788  public static function unlink_tempfile($uploadedTempFileName)
2789  {
2790  if ($uploadedTempFileName) {
2791  $uploadedTempFileName = self::fixWindowsFilePath($uploadedTempFileName);
2792  if (
2793  self::validPathStr($uploadedTempFileName)
2794  && (
2795  str_starts_with($uploadedTempFileName, ‪Environment::getPublicPath() . '/typo3temp/')
2796  || str_starts_with($uploadedTempFileName, ‪Environment::getVarPath() . '/')
2797  )
2798  && @is_file($uploadedTempFileName)
2799  ) {
2800  if (unlink($uploadedTempFileName)) {
2801  return true;
2802  }
2803  }
2804  }
2805 
2806  return null;
2807  }
2808 
2820  public static function tempnam($filePrefix, $fileSuffix = '')
2821  {
2822  $temporaryPath = ‪Environment::getVarPath() . '/transient/';
2823  if (!is_dir($temporaryPath)) {
2824  ‪self::mkdir_deep($temporaryPath);
2825  }
2826  if ($fileSuffix === '') {
2827  $path = (string)tempnam($temporaryPath, $filePrefix);
2828  $tempFileName = $temporaryPath . ‪PathUtility::basename($path);
2829  } else {
2830  do {
2831  $tempFileName = $temporaryPath . $filePrefix . random_int(1, PHP_INT_MAX) . $fileSuffix;
2832  } while (file_exists($tempFileName));
2833  touch($tempFileName);
2834  clearstatcache(false, $tempFileName);
2835  }
2836  return $tempFileName;
2837  }
2838 
2849  public static function callUserFunction($funcName, &$params, ?object $ref = null)
2850  {
2851  // Check if we're using a closure and invoke it directly.
2852  if (is_object($funcName) && is_a($funcName, \Closure::class)) {
2853  return call_user_func_array($funcName, [&$params, &$ref]);
2854  }
2855  $funcName = trim($funcName);
2856  $parts = explode('->', $funcName);
2857  // Call function or method
2858  if (count($parts) === 2) {
2859  // It's a class/method
2860  // Check if class/method exists:
2861  if (class_exists($parts[0])) {
2862  // Create object
2863  $classObj = self::makeInstance($parts[0]);
2864  $methodName = (string)$parts[1];
2865  $callable = [$classObj, $methodName];
2866  if (is_callable($callable)) {
2867  // Call method:
2868  $content = call_user_func_array($callable, [&$params, &$ref]);
2869  } else {
2870  throw new \InvalidArgumentException('No method name \'' . $parts[1] . '\' in class ' . $parts[0], 1294585865);
2871  }
2872  } else {
2873  throw new \InvalidArgumentException('No class named ' . $parts[0], 1294585866);
2874  }
2875  } elseif (function_exists($funcName) && is_callable($funcName)) {
2876  // It's a function
2877  $content = call_user_func_array($funcName, [&$params, &$ref]);
2878  } else {
2879  // Usually this will be annotated by static code analysis tools, but there's no native "not empty string" type
2880  throw new \InvalidArgumentException('No function named: ' . $funcName, 1294585867);
2881  }
2882  return $content;
2883  }
2884 
2888  public static function setContainer(ContainerInterface ‪$container): void
2889  {
2890  self::$container = ‪$container;
2891  }
2892 
2896  public static function getContainer(): ContainerInterface
2897  {
2898  if (self::$container === null) {
2899  throw new \LogicException('PSR-11 Container is not available', 1549404144);
2900  }
2901  return ‪self::$container;
2902  }
2903 
2918  public static function makeInstance(string $className, mixed ...$constructorArguments): object
2919  {
2920  // PHPStan will complain about this check. That's okay as we're checking a contract violation here.
2921  if ($className === '') {
2922  throw new \InvalidArgumentException('$className must be a non empty string.', 1288965219);
2923  }
2924  // Never instantiate with a beginning backslash, otherwise things like singletons won't work.
2925  if (str_starts_with($className, '\\')) {
2926  throw new \InvalidArgumentException(
2927  '$className "' . $className . '" must not start with a backslash.',
2928  1420281366
2929  );
2930  }
2931  if (isset(static::$finalClassNameCache[$className])) {
2932  $finalClassName = static::$finalClassNameCache[$className];
2933  } else {
2934  $finalClassName = self::getClassName($className);
2935  static::$finalClassNameCache[$className] = $finalClassName;
2936  }
2937  // Return singleton instance if it is already registered
2938  if (isset(self::$singletonInstances[$finalClassName])) {
2939  return self::$singletonInstances[$finalClassName];
2940  }
2941  // Return instance if it has been injected by addInstance()
2942  if (
2943  isset(self::$nonSingletonInstances[$finalClassName])
2944  && !empty(self::$nonSingletonInstances[$finalClassName])
2945  ) {
2946  return array_shift(self::$nonSingletonInstances[$finalClassName]);
2947  }
2948 
2949  // Read service and prototypes from the DI container, this is required to
2950  // support classes that require dependency injection.
2951  // We operate on the original class name on purpose, as class overrides
2952  // are resolved inside the container
2953  if (self::$container !== null && $constructorArguments === [] && self::$container->has($className)) {
2954  return self::$container->get($className);
2955  }
2956 
2957  // Create new instance and call constructor with parameters
2958  $instance = new $finalClassName(...$constructorArguments);
2959  // Register new singleton instance, but only if it is not a known PSR-11 container service
2960  if ($instance instanceof SingletonInterface && !(self::$container !== null && self::$container->has($className))) {
2961  self::$singletonInstances[$finalClassName] = $instance;
2962  }
2963  if ($instance instanceof LoggerAwareInterface) {
2964  $instance->setLogger(static::makeInstance(LogManager::class)->getLogger($className));
2965  }
2966  return $instance;
2967  }
2968 
2980  public static function makeInstanceForDi(string $className, ...$constructorArguments): object
2981  {
2982  $finalClassName = static::$finalClassNameCache[$className] ?? static::$finalClassNameCache[$className] = self::getClassName($className);
2983 
2984  // Return singleton instance if it is already registered (currently required for unit and functional tests)
2985  if (isset(self::$singletonInstances[$finalClassName])) {
2986  return self::$singletonInstances[$finalClassName];
2987  }
2988  // Create new instance and call constructor with parameters
2989  return new $finalClassName(...$constructorArguments);
2990  }
2991 
3001  public static function getClassName($className)
3002  {
3003  if (class_exists($className)) {
3004  while (static::classHasImplementation($className)) {
3005  $className = static::getImplementationForClass($className);
3006  }
3007  }
3009  }
3010 
3017  protected static function getImplementationForClass($className)
3018  {
3019  return ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][$className]['className'];
3020  }
3021 
3028  protected static function classHasImplementation($className)
3029  {
3030  return !empty(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][$className]['className']);
3031  }
3032 
3049  public static function setSingletonInstance($className, SingletonInterface $instance)
3050  {
3051  self::checkInstanceClassName($className, $instance);
3052  // Check for XCLASS registration (same is done in makeInstance() in order to store the singleton of the final class name)
3053  $finalClassName = self::getClassName($className);
3054  self::$singletonInstances[$finalClassName] = $instance;
3055  }
3056 
3071  public static function removeSingletonInstance($className, SingletonInterface $instance)
3072  {
3073  self::checkInstanceClassName($className, $instance);
3074  if (!isset(self::$singletonInstances[$className])) {
3075  throw new \InvalidArgumentException('No Instance registered for ' . $className . '.', 1394099179);
3076  }
3077  if ($instance !== self::$singletonInstances[$className]) {
3078  throw new \InvalidArgumentException('The instance you are trying to remove has not been registered before.', 1394099256);
3079  }
3080  unset(self::$singletonInstances[$className]);
3081  }
3082 
3096  public static function resetSingletonInstances(array $newSingletonInstances)
3097  {
3098  static::$singletonInstances = [];
3099  foreach ($newSingletonInstances as $className => $instance) {
3100  static::setSingletonInstance($className, $instance);
3101  }
3102  }
3103 
3116  public static function getSingletonInstances()
3117  {
3118  return static::$singletonInstances;
3119  }
3120 
3132  public static function getInstances()
3133  {
3134  return static::$nonSingletonInstances;
3135  }
3136 
3151  public static function addInstance($className, $instance)
3152  {
3153  self::checkInstanceClassName($className, $instance);
3154  if ($instance instanceof SingletonInterface) {
3155  throw new \InvalidArgumentException('$instance must not be an instance of TYPO3\\CMS\\Core\\SingletonInterface. For setting singletons, please use setSingletonInstance.', 1288969325);
3156  }
3157  if (!isset(self::$nonSingletonInstances[$className])) {
3158  self::$nonSingletonInstances[$className] = [];
3159  }
3160  self::$nonSingletonInstances[$className][] = $instance;
3161  }
3162 
3170  protected static function checkInstanceClassName($className, $instance)
3171  {
3172  if ($className === '') {
3173  throw new \InvalidArgumentException('$className must not be empty.', 1288967479);
3174  }
3175  if (!$instance instanceof $className) {
3176  throw new \InvalidArgumentException('$instance must be an instance of ' . $className . ', but actually is an instance of ' . get_class($instance) . '.', 1288967686);
3177  }
3178  }
3179 
3190  public static function purgeInstances()
3191  {
3192  self::$container = null;
3193  self::$singletonInstances = [];
3194  self::$nonSingletonInstances = [];
3195  }
3196 
3206  public static function flushInternalRuntimeCaches()
3207  {
3208  self::$finalClassNameCache = [];
3209  self::$indpEnvCache = [];
3210  }
3211 
3226  public static function makeInstanceService(string $serviceType, string $serviceSubType = '', array $excludeServiceKeys = []): array|object|false
3227  {
3228  $error = false;
3229  $requestInfo = [
3230  'requestedServiceType' => $serviceType,
3231  'requestedServiceSubType' => $serviceSubType,
3232  'requestedExcludeServiceKeys' => $excludeServiceKeys,
3233  ];
3234  while ($info = ‪ExtensionManagementUtility::findService($serviceType, $serviceSubType, $excludeServiceKeys)) {
3235  // provide information about requested service to service object
3236  $info = array_merge($info, $requestInfo);
3237 
3239  $className = $info['className'];
3241  $obj = self::makeInstance($className);
3242  if (is_object($obj)) {
3243  if (!is_callable([$obj, 'init'])) {
3244  self::getLogger()->error('Requested service {class} has no init() method.', [
3245  'class' => $info['className'],
3246  'service' => $info,
3247  ]);
3248  throw new \RuntimeException('Broken service: ' . $info['className'], 1568119209);
3249  }
3250  $obj->info = $info;
3251  // service available?
3252  if ($obj->init()) {
3253  return $obj;
3254  }
3255  $error = $obj->getLastErrorArray();
3256  unset($obj);
3257  }
3258 
3259  // deactivate the service
3260  ‪ExtensionManagementUtility::deactivateService($info['serviceType'], $info['serviceKey']);
3261  }
3262  return $error;
3263  }
3264 
3271  public static function quoteJSvalue($value)
3272  {
3273  $json = (string)json_encode(
3274  (string)$value,
3275  JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_TAG
3276  );
3277 
3278  return strtr(
3279  $json,
3280  [
3281  '"' => '\'',
3282  '\\\\' => '\\u005C',
3283  ' ' => '\\u0020',
3284  '!' => '\\u0021',
3285  '\\t' => '\\u0009',
3286  '\\n' => '\\u000A',
3287  '\\r' => '\\u000D',
3288  ]
3289  );
3290  }
3291 
3301  public static function jsonEncodeForHtmlAttribute($value, bool $useHtmlEntities = true): string
3302  {
3303  $json = (string)json_encode($value, JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_TAG);
3304  return $useHtmlEntities ? htmlspecialchars($json) : $json;
3305  }
3306 
3315  public static function jsonEncodeForJavaScript($value): string
3316  {
3317  $json = (string)json_encode($value, JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_TAG);
3318  return strtr(
3319  $json,
3320  [
3321  // comments below refer to JSON-encoded data
3322  '\\\\' => '\\\\u005C', // `"\\Vendor\\Package"` -> `"\\u005CVendor\\u005CPackage"`
3323  '\\t' => '\\u0009', // `"\t"` -> `"\u0009"`
3324  '\\n' => '\\u000A', // `"\n"` -> `"\u000A"`
3325  '\\r' => '\\u000D', // `"\r"` -> `"\u000D"`
3326  ]
3327  );
3328  }
3329 
3333  protected static function getLogger()
3334  {
3335  return static::makeInstance(LogManager::class)->getLogger(__CLASS__);
3336  }
3337 }
‪TYPO3\CMS\Core\Utility\GeneralUtility\underscoredToLowerCamelCase
‪static string underscoredToLowerCamelCase($string)
Definition: GeneralUtility.php:755
‪TYPO3\CMS\Core\Utility\GeneralUtility\trimExplode
‪static list< string > trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
Definition: GeneralUtility.php:916
‪TYPO3\CMS\Core\Utility\GeneralUtility\xml2array
‪static mixed xml2array($string, $NSprefix='', $reportDocTag=false)
Definition: GeneralUtility.php:1363
‪TYPO3\CMS\Core\Utility\GeneralUtility\validIPv6
‪static bool validIPv6($ip)
Definition: GeneralUtility.php:439
‪TYPO3\CMS\Core\Utility\GeneralUtility\validIP
‪static bool validIP($ip)
Definition: GeneralUtility.php:413
‪TYPO3\CMS\Core\Utility\PathUtility\stripPathSitePrefix
‪static stripPathSitePrefix(string $path)
Definition: PathUtility.php:428
‪TYPO3\CMS\Core\Utility\GeneralUtility\$nl
‪$nl
Definition: GeneralUtility.php:1251
‪TYPO3\CMS\Core\Utility\PathUtility\isExtensionPath
‪static isExtensionPath(string $path)
Definition: PathUtility.php:117
‪TYPO3\CMS\Core\Utility\GeneralUtility\$container
‪static ContainerInterface $container
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Core\Utility\HttpUtility\buildUrl
‪static buildUrl(array $urlParts)
Definition: HttpUtility.php:102
‪TYPO3\CMS\Core\Utility\GeneralUtility\fixed_lgd_cs
‪static string fixed_lgd_cs(string $string, int $chars, string $appendString='...')
Definition: GeneralUtility.php:202
‪TYPO3\CMS\Core\Utility\PathUtility\isAbsolutePath
‪static isAbsolutePath(string $path)
Definition: PathUtility.php:286
‪TYPO3
‪TYPO3\CMS\Core\Utility\GeneralUtility\_POST
‪static mixed _POST($var=null)
Definition: GeneralUtility.php:174
‪TYPO3\CMS\Core\Core\Environment\getPublicPath
‪static getPublicPath()
Definition: Environment.php:187
‪TYPO3\CMS\Core\Utility\GeneralUtility\$spaceInd
‪static array< string, function explodeUrl2Array( $string) { $output=[];$p=explode('&', $string);foreach( $p as $v) { if( $v !=='') { $nameAndValue=explode('=', $v, 2);$output[rawurldecode( $nameAndValue[0])]=isset( $nameAndValue[1]) ? rawurldecode( $nameAndValue[1]) :'';} } return $output;} public static array function removeDotsFromTS(array $ts):array { $out=[];foreach( $ts as $key=> $value) { if(is_array( $value)) { $key=rtrim( $key, '.');$out[ $key]=self::removeDotsFromTS( $value);} else { $out[ $key]=$value;} } return $out;} public static array< string, function get_tag_attributes( $tag, bool $decodeEntities=false) { $components=self::split_tag_attributes( $tag);$name='';$valuemode=false;$attributes=[];foreach( $components as $key=> $val) { if( $val !=='=') { if( $valuemode) { if( $name) { $attributes[ $name]=$decodeEntities ? htmlspecialchars_decode( $val) :$val;$name='';} } else { if( $key=strtolower(preg_replace('/[^[:alnum:]_\\:\\-]/', '', $val) ?? '')) { $attributes[ $key]='';$name=$key;} } $valuemode=false;} else { $valuemode=true;} } return $attributes;} public static string[] function split_tag_attributes( $tag) { $tag_tmp=trim(preg_replace('/^<[^[:space:]] */', '', trim( $tag)) ?? '');$tag_tmp=trim(rtrim( $tag_tmp, '>'));$value=[];while( $tag_tmp !=='') { $firstChar=$tag_tmp[0];if( $firstChar==='"' || $firstChar === '\'') { $reg = explode($firstChar, $tag_tmp, 3); $value[] = $reg[1]; $tag_tmp = trim($reg[2] ?? ''); } elseif ($firstChar === '=') { $value[] = '='; $tag_tmp = trim(substr($tag_tmp, 1)); } else { $reg = preg_split('/[[:space:]=]/', $tag_tmp, 2); $value[] = trim($reg[0]); $tag_tmp = trim(substr($tag_tmp, strlen($reg[0]), 1) . ($reg[1] ?? '')); } } reset($value); return $value; } public static string function implodeAttributes(array $arr, $xhtmlSafe = false, $keepBlankAttributes = false) { if ($xhtmlSafe) { $newArr = []; foreach ($arr as $attributeName => $attributeValue) { $attributeName = strtolower($attributeName); if (!isset($newArr[$attributeName])) { $newArr[$attributeName] = htmlspecialchars((string)$attributeValue); } } $arr = $newArr; } $list = []; foreach ($arr as $attributeName => $attributeValue) { if ((string)$attributeValue !== '' || $keepBlankAttributes) { $list[] = $attributeName . '="' . $attributeValue . '"'; } } return implode(' ', $list); } public static string function wrapJS(string $string, array $attributes = []) { if (trim($string)) { $string = ltrim($string, LF); $match = []; if (preg_match('/^(\\t+)/', $string, $match)) { $string = str_replace($match[1], "\t", $string); } return '<script ' . GeneralUtility::implodeAttributes($attributes, true) . '>' . $string . '</script>'; } return ''; } public static mixed function xml2tree($string, $depth = 999, $parserOptions = []) { $parser = xml_parser_create(); $vals = []; $index = []; xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0); xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 0); foreach ($parserOptions as $option => $value) { xml_parser_set_option($parser, $option, $value); } xml_parse_into_struct($parser, $string, $vals, $index); if (xml_get_error_code($parser)) { return 'Line ' . xml_get_current_line_number($parser) . ': ' . xml_error_string(xml_get_error_code($parser)); } xml_parser_free($parser); $stack = [[]]; $stacktop = 0; $startPoint = 0; $tagi = []; foreach ($vals as $key => $val) { $type = $val['type']; if ($type === 'open' || $type === 'complete') { $stack[$stacktop++] = $tagi; if ($depth == $stacktop) { $startPoint = $key; } $tagi = ['tag' => $val['tag']]; if (isset($val['attributes'])) { $tagi['attrs'] = $val['attributes']; } if (isset($val['value'])) { $tagi['values'][] = $val['value']; } } if ($type === 'complete' || $type === 'close') { $oldtagi = $tagi; $tagi = $stack[--$stacktop]; $oldtag = $oldtagi['tag']; unset($oldtagi['tag']); if ($depth == $stacktop + 1) { if ($key - $startPoint > 0) { $partArray = array_slice($vals, $startPoint + 1, $key - $startPoint - 1); $oldtagi['XMLvalue'] = self::xmlRecompileFromStructValArray($partArray); } else { $oldtagi['XMLvalue'] = $oldtagi['values'][0]; } } $tagi['ch'][$oldtag][] = $oldtagi; unset($oldtagi); } if ($type === 'cdata') { $tagi['values'][] = $val['value']; } } return $tagi['ch']; } public static string function array2xml(array $array, $NSprefix = '', $level = 0, $docTag = 'phparray', $spaceInd = 0, array $options = [], array $stackData = []) { $binaryChars = "\0" . chr(1) . chr(2) . chr(3) . chr(4) . chr(5) . chr(6) . chr(7) . chr(8) . chr(11) . chr(12) . chr(14) . chr(15) . chr(16) . chr(17) . chr(18) . chr(19) . chr(20) . chr(21) . chr(22) . chr(23) . chr(24) . chr(25) . chr(26) . chr(27) . chr(28) . chr(29) . chr(30) . chr(31); $indentChar = $spaceInd ? ' ' : "\t"; $indentN = $spaceInd > $spaceInd
Definition: GeneralUtility.php:1250
‪TYPO3\CMS\Core\Utility\GeneralUtility\$localeInfo
‪$localeInfo
Definition: GeneralUtility.php:672
‪TYPO3\CMS\Core\Utility\GeneralUtility\_GP
‪static mixed _GP($var)
Definition: GeneralUtility.php:107
‪TYPO3\CMS\Core\Utility
Definition: ArrayUtility.php:18
‪TYPO3\CMS\Core\Core\ClassLoadingInformation
Definition: ClassLoadingInformation.php:35
‪TYPO3\CMS\Core\Utility\GeneralUtility\$labelArr
‪if($base !==1000 && $base !==1024) $labelArr
Definition: GeneralUtility.php:669
‪$parser
‪$parser
Definition: annotationChecker.php:108
‪TYPO3\CMS\Core\Core\Environment\getCurrentScript
‪static getCurrentScript()
Definition: Environment.php:220
‪TYPO3\CMS\Core\Utility\GeneralUtility\$multiplier
‪$multiplier
Definition: GeneralUtility.php:674
‪TYPO3\CMS\Core\Utility\ExtensionManagementUtility\deactivateService
‪static deactivateService(string $serviceType, string $serviceKey)
Definition: ExtensionManagementUtility.php:946
‪TYPO3\CMS\Core\Utility\GeneralUtility\camelCaseToLowerCaseUnderscored
‪static string camelCaseToLowerCaseUnderscored($string)
Definition: GeneralUtility.php:767
‪$dir
‪$dir
Definition: validateRstFiles.php:257
‪TYPO3\CMS\Core\Utility\GeneralUtility\createDirectoryPath
‪static string createDirectoryPath($fullDirectoryPath)
Definition: GeneralUtility.php:1779
‪TYPO3\CMS\Core\Authentication\AbstractAuthenticationService
Definition: AbstractAuthenticationService.php:29
‪TYPO3\CMS\Core\Core\Environment\getVarPath
‪static getVarPath()
Definition: Environment.php:197
‪TYPO3\CMS\Core\Utility\GeneralUtility\normalizeIPv6
‪static string normalizeIPv6($address)
Definition: GeneralUtility.php:349
‪TYPO3\CMS\Core\Utility\PathUtility\basename
‪static basename(string $path)
Definition: PathUtility.php:219
‪TYPO3\CMS\Core\Utility\GeneralUtility\$nonSingletonInstances
‪static array $nonSingletonInstances
Definition: GeneralUtility.php:66
‪TYPO3\CMS\Core\Utility\GeneralUtility\implodeArrayForUrl
‪static string implodeArrayForUrl($name, array $theArray, $str='', $skipBlank=false, $rawurlencodeParamName=false)
Definition: GeneralUtility.php:954
‪TYPO3\CMS\Core\Utility\GeneralUtility\get_dirs
‪static string[] string null get_dirs($path)
Definition: GeneralUtility.php:1863
‪TYPO3\CMS\Core\Utility\GeneralUtility\$indpEnvCache
‪static array $indpEnvCache
Definition: GeneralUtility.php:79
‪TYPO3\CMS\Core\Utility\GeneralUtility\revExplode
‪static list< string > revExplode($delimiter, $string, $limit=0)
Definition: GeneralUtility.php:882
‪TYPO3\CMS\Core\Utility\GeneralUtility\getUrl
‪static string false getUrl($url)
Definition: GeneralUtility.php:1542
‪TYPO3\CMS\Core\Utility\MathUtility\canBeInterpretedAsInteger
‪static bool canBeInterpretedAsInteger(mixed $var)
Definition: MathUtility.php:69
‪TYPO3\CMS\Core\Utility\GeneralUtility\$sizeInUnits
‪$sizeInUnits
Definition: GeneralUtility.php:675
‪TYPO3\CMS\Core\Utility\GeneralUtility\cmpFQDN
‪static bool cmpFQDN($baseHost, $list)
Definition: GeneralUtility.php:451
‪TYPO3\CMS\Core\Core\Environment\getProjectPath
‪static string getProjectPath()
Definition: Environment.php:160
‪TYPO3\CMS\Core\Utility\GeneralUtility\$output
‪foreach($array as $k=> $v) if(! $level) return $output
Definition: GeneralUtility.php:1345
‪TYPO3\CMS\Core\Utility\GeneralUtility\fixPermissions
‪static mixed fixPermissions($path, $recursive=false)
Definition: GeneralUtility.php:1594
‪TYPO3\CMS\Core\Utility\GeneralUtility\mkdir_deep
‪static mkdir_deep($directory)
Definition: GeneralUtility.php:1753
‪TYPO3\CMS\Core\Utility\GeneralUtility\cmpIP
‪static bool cmpIP($baseIP, $list)
Definition: GeneralUtility.php:223
‪TYPO3\CMS\Core\Utility\GeneralUtility\expandList
‪static string expandList($list)
Definition: GeneralUtility.php:544
‪TYPO3\CMS\Core\Utility\ExtensionManagementUtility\findService
‪static array false findService(string $serviceType, string $serviceSubType='', array $excludeServiceKeys=[])
Definition: ExtensionManagementUtility.php:846
‪$validator
‪if(isset($args['d'])) $validator
Definition: validateRstFiles.php:262
‪TYPO3\CMS\Core\Utility\GeneralUtility\$singletonInstances
‪static array $singletonInstances
Definition: GeneralUtility.php:59
‪TYPO3\CMS\Core\Utility\GeneralUtility\_GET
‪static mixed _GET($var=null)
Definition: GeneralUtility.php:155
‪TYPO3\CMS\Core\Utility\PathUtility\dirnameDuringBootstrap
‪static string dirnameDuringBootstrap(string $path)
Definition: PathUtility.php:337
‪TYPO3\CMS\Core\Utility\GeneralUtility\hmac
‪static string hmac($input, $additionalSecret='')
Definition: GeneralUtility.php:584
‪TYPO3\CMS\Core\Cache\CacheManager
Definition: CacheManager.php:36
‪TYPO3\CMS\Core\Utility\GeneralUtility\cmpIPv6
‪static bool cmpIPv6($baseIP, $list)
Definition: GeneralUtility.php:294
‪TYPO3\CMS\Core\Utility\GeneralUtility\validIPv4
‪static bool validIPv4($ip)
Definition: GeneralUtility.php:426
‪TYPO3\CMS\Core\Utility\GeneralUtility\xml2arrayProcess
‪static mixed xml2arrayProcess($string, $NSprefix='', $reportDocTag=false)
Definition: GeneralUtility.php:1386
‪TYPO3\CMS\Core\Core\ClassLoadingInformation\getClassNameForAlias
‪static class string getClassNameForAlias($alias)
Definition: ClassLoadingInformation.php:201
‪TYPO3\CMS\Core\Utility\GeneralUtility\$sizeInBytes
‪$sizeInBytes
Definition: GeneralUtility.php:673
‪TYPO3\CMS\Core\Http\RequestFactory
Definition: RequestFactory.php:30
‪TYPO3\CMS\Core\Utility\GeneralUtility\$finalClassNameCache
‪static array $finalClassNameCache
Definition: GeneralUtility.php:74
‪TYPO3\CMS\Core\Utility\GeneralUtility\$output
‪$output
Definition: GeneralUtility.php:1253
‪TYPO3\CMS\Core\Utility\GeneralUtility\isValidUrl
‪static bool isValidUrl($url)
Definition: GeneralUtility.php:797
‪TYPO3\CMS\Webhooks\Message\$url
‪identifier readonly UriInterface $url
Definition: LoginErrorOccurredMessage.php:36
‪TYPO3\CMS\Core\SingletonInterface
Definition: SingletonInterface.php:23
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Core\Log\LogManager
Definition: LogManager.php:33
‪TYPO3\CMS\Core\Core\Environment
Definition: Environment.php:41
‪TYPO3\CMS\Core\Utility\GeneralUtility\intExplode
‪static int[] intExplode($delimiter, $string, $removeEmptyValues=false, $limit=0)
Definition: GeneralUtility.php:842
‪TYPO3\CMS\Core\Utility\GeneralUtility\cmpIPv4
‪static bool cmpIPv4($baseIP, $list)
Definition: GeneralUtility.php:245
‪TYPO3\CMS\Core\Utility\GeneralUtility\rmdir
‪static bool rmdir($path, $removeNonEmpty=false)
Definition: GeneralUtility.php:1806
‪TYPO3\CMS\Core\Utility\GeneralUtility\_GPmerged
‪static array _GPmerged($parameter)
Definition: GeneralUtility.php:134
‪TYPO3\CMS\Core\Utility\GeneralUtility\inList
‪static bool inList($list, $item)
Definition: GeneralUtility.php:532
‪TYPO3\CMS\Core\Utility\ExtensionManagementUtility\resolvePackagePath
‪static resolvePackagePath(string $path)
Definition: ExtensionManagementUtility.php:108
‪TYPO3\CMS\Core\Utility\GeneralUtility\xmlRecompileFromStructValArray
‪static string xmlRecompileFromStructValArray(array $vals)
Definition: GeneralUtility.php:1492
‪TYPO3\CMS\Core\Package\Exception
Definition: InvalidPackageKeyException.php:16
‪TYPO3\CMS\Core\Http\fromRequest
‪@ fromRequest
Definition: ApplicationType.php:67
‪TYPO3\CMS\Core\Utility\GeneralUtility\isOnCurrentHost
‪static bool isOnCurrentHost($url)
Definition: GeneralUtility.php:519
‪TYPO3\CMS\Core\Utility\GeneralUtility\md5int
‪static int md5int($str)
Definition: GeneralUtility.php:572
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:51
‪TYPO3\CMS\Core\Utility\GeneralUtility\mkdir
‪static bool mkdir($newFolder)
Definition: GeneralUtility.php:1736
‪TYPO3\CMS\Core\Utility\GeneralUtility\writeFile
‪static bool writeFile($file, $content, $changePermissions=false)
Definition: GeneralUtility.php:1567
‪TYPO3\CMS\Core\Utility\GeneralUtility\underscoredToUpperCamelCase
‪static string underscoredToUpperCamelCase($string)
Definition: GeneralUtility.php:743
‪TYPO3\CMS\Core\Utility\GeneralUtility\__construct
‪__construct()
Definition: GeneralUtility.php:81
‪TYPO3\CMS\Webhooks\Message\$identifier
‪identifier readonly string $identifier
Definition: FileAddedMessage.php:37
‪TYPO3\CMS\Core\Http\ApplicationType
‪ApplicationType
Definition: ApplicationType.php:56
‪TYPO3\CMS\Core\Utility\GeneralUtility\writeFileToTypo3tempDir
‪static string null writeFileToTypo3tempDir($filepath, $content)
Definition: GeneralUtility.php:1659
‪TYPO3\CMS\Core\Core\Environment\isWindows
‪static isWindows()
Definition: Environment.php:287