‪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 
93  public static function ‪fixed_lgd_cs(string $string, int $chars, string $appendString = '...'): string
94  {
95  if ($chars === 0 || mb_strlen($string, 'utf-8') <= abs($chars)) {
96  return $string;
97  }
98  if ($chars > 0) {
99  $string = mb_substr($string, 0, $chars, 'utf-8') . $appendString;
100  } else {
101  $string = $appendString . mb_substr($string, $chars, mb_strlen($string, 'utf-8'), 'utf-8');
102  }
103  return $string;
104  }
105 
114  public static function ‪cmpIP(string $baseIP, string $list): bool
115  {
116  $list = trim($list);
117  if ($list === '') {
118  return false;
119  }
120  if ($list === '*') {
121  return true;
122  }
123  if (str_contains($baseIP, ':') && self::validIPv6($baseIP)) {
124  return ‪self::cmpIPv6($baseIP, $list);
125  }
126  return ‪self::cmpIPv4($baseIP, $list);
127  }
128 
136  public static function ‪cmpIPv4(string $baseIP, string $list): bool
137  {
138  $IPpartsReq = explode('.', $baseIP);
139  if (count($IPpartsReq) === 4) {
140  $values = self::trimExplode(',', $list, true);
141  foreach ($values as $test) {
142  $testList = explode('/', $test);
143  if (count($testList) === 2) {
144  [$test, $mask] = $testList;
145  } else {
146  $mask = false;
147  }
148  if ((int)$mask) {
149  $mask = (int)$mask;
150  // "192.168.3.0/24"
151  $lnet = (int)ip2long($test);
152  $lip = (int)ip2long($baseIP);
153  $binnet = str_pad(decbin($lnet), 32, '0', STR_PAD_LEFT);
154  $firstpart = substr($binnet, 0, $mask);
155  $binip = str_pad(decbin($lip), 32, '0', STR_PAD_LEFT);
156  $firstip = substr($binip, 0, $mask);
157  $yes = $firstpart === $firstip;
158  } else {
159  // "192.168.*.*"
160  $IPparts = explode('.', $test);
161  $yes = 1;
162  foreach ($IPparts as $index => $val) {
163  $val = trim($val);
164  if ($val !== '*' && $IPpartsReq[$index] !== $val) {
165  $yes = 0;
166  }
167  }
168  }
169  if ($yes) {
170  return true;
171  }
172  }
173  }
174  return false;
175  }
176 
185  public static function ‪cmpIPv6(string $baseIP, string $list): bool
186  {
187  // Policy default: Deny connection
188  $success = false;
189  $baseIP = ‪self::normalizeIPv6($baseIP);
190  $values = self::trimExplode(',', $list, true);
191  foreach ($values as $test) {
192  $testList = explode('/', $test);
193  if (count($testList) === 2) {
194  [$test, $mask] = $testList;
195  } else {
196  $mask = false;
197  }
198  if (self::validIPv6($test)) {
199  $test = ‪self::normalizeIPv6($test);
200  $maskInt = (int)$mask ?: 128;
201  // Special case; /0 is an allowed mask - equals a wildcard
202  if ($mask === '0') {
203  $success = true;
204  } elseif ($maskInt == 128) {
205  $success = $test === $baseIP;
206  } else {
207  $testBin = (string)inet_pton($test);
208  $baseIPBin = (string)inet_pton($baseIP);
209 
210  $success = true;
211  // Modulo is 0 if this is a 8-bit-boundary
212  $maskIntModulo = $maskInt % 8;
213  $numFullCharactersUntilBoundary = (int)($maskInt / 8);
214  $substring = (string)substr($baseIPBin, 0, $numFullCharactersUntilBoundary);
215  if (!str_starts_with($testBin, $substring)) {
216  $success = false;
217  } elseif ($maskIntModulo > 0) {
218  // If not an 8-bit-boundary, check bits of last character
219  $testLastBits = str_pad(decbin(ord(substr($testBin, $numFullCharactersUntilBoundary, 1))), 8, '0', STR_PAD_LEFT);
220  $baseIPLastBits = str_pad(decbin(ord(substr($baseIPBin, $numFullCharactersUntilBoundary, 1))), 8, '0', STR_PAD_LEFT);
221  if (strncmp($testLastBits, $baseIPLastBits, $maskIntModulo) != 0) {
222  $success = false;
223  }
224  }
225  }
226  }
227  if ($success) {
228  return true;
229  }
230  }
231  return false;
232  }
233 
240  public static function ‪normalizeIPv6(string $address): string
241  {
242  $normalizedAddress = '';
243  // According to RFC lowercase-representation is recommended
244  $address = strtolower($address);
245  // Normalized representation has 39 characters (0000:0000:0000:0000:0000:0000:0000:0000)
246  if (strlen($address) === 39) {
247  // Already in full expanded form
248  return $address;
249  }
250  // Count 2 if if address has hidden zero blocks
251  $chunks = explode('::', $address);
252  if (count($chunks) === 2) {
253  $chunksLeft = explode(':', $chunks[0]);
254  $chunksRight = explode(':', $chunks[1]);
255  $left = count($chunksLeft);
256  $right = count($chunksRight);
257  // Special case: leading zero-only blocks count to 1, should be 0
258  if ($left === 1 && strlen($chunksLeft[0]) === 0) {
259  $left = 0;
260  }
261  $hiddenBlocks = 8 - ($left + $right);
262  $hiddenPart = '';
263  $h = 0;
264  while ($h < $hiddenBlocks) {
265  $hiddenPart .= '0000:';
266  $h++;
267  }
268  if ($left === 0) {
269  $stageOneAddress = $hiddenPart . $chunks[1];
270  } else {
271  $stageOneAddress = $chunks[0] . ':' . $hiddenPart . $chunks[1];
272  }
273  } else {
274  $stageOneAddress = $address;
275  }
276  // Normalize the blocks:
277  $blocks = explode(':', $stageOneAddress);
278  $divCounter = 0;
279  foreach ($blocks as $block) {
280  $tmpBlock = '';
281  $i = 0;
282  $hiddenZeros = 4 - strlen($block);
283  while ($i < $hiddenZeros) {
284  $tmpBlock .= '0';
285  $i++;
286  }
287  $normalizedAddress .= $tmpBlock . $block;
288  if ($divCounter < 7) {
289  $normalizedAddress .= ':';
290  $divCounter++;
291  }
292  }
293  return $normalizedAddress;
294  }
295 
304  public static function ‪validIP(string $ip): bool
305  {
306  return filter_var($ip, FILTER_VALIDATE_IP) !== false;
307  }
308 
317  public static function ‪validIPv4(string $ip): bool
318  {
319  return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false;
320  }
321 
330  public static function ‪validIPv6(string $ip): bool
331  {
332  return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false;
333  }
334 
342  public static function ‪cmpFQDN(string $baseHost, string $list): bool
343  {
344  $baseHost = trim($baseHost);
345  if (empty($baseHost)) {
346  return false;
347  }
348  if (self::validIPv4($baseHost) || self::validIPv6($baseHost)) {
349  // Resolve hostname
350  // Note: this is reverse-lookup and can be randomly set as soon as somebody is able to set
351  // the reverse-DNS for his IP (security when for example used with REMOTE_ADDR)
352  $baseHostName = (string)gethostbyaddr($baseHost);
353  if ($baseHostName === $baseHost) {
354  // Unable to resolve hostname
355  return false;
356  }
357  } else {
358  $baseHostName = $baseHost;
359  }
360  $baseHostNameParts = explode('.', $baseHostName);
361  $values = self::trimExplode(',', $list, true);
362  foreach ($values as $test) {
363  $hostNameParts = explode('.', $test);
364  // To match hostNameParts can only be shorter (in case of wildcards) or equal
365  $hostNamePartsCount = count($hostNameParts);
366  $baseHostNamePartsCount = count($baseHostNameParts);
367  if ($hostNamePartsCount > $baseHostNamePartsCount) {
368  continue;
369  }
370  $yes = true;
371  foreach ($hostNameParts as $index => $val) {
372  $val = trim($val);
373  if ($val === '*') {
374  // Wildcard valid for one or more hostname-parts
375  $wildcardStart = $index + 1;
376  // Wildcard as last/only part always matches, otherwise perform recursive checks
377  if ($wildcardStart < $hostNamePartsCount) {
378  $wildcardMatched = false;
379  $tempHostName = implode('.', array_slice($hostNameParts, $index + 1));
380  while ($wildcardStart < $baseHostNamePartsCount && !$wildcardMatched) {
381  $tempBaseHostName = implode('.', array_slice($baseHostNameParts, $wildcardStart));
382  $wildcardMatched = ‪self::cmpFQDN($tempBaseHostName, $tempHostName);
383  $wildcardStart++;
384  }
385  if ($wildcardMatched) {
386  // Match found by recursive compare
387  return true;
388  }
389  $yes = false;
390  }
391  } elseif ($baseHostNameParts[$index] !== $val) {
392  // In case of no match
393  $yes = false;
394  }
395  }
396  if ($yes) {
397  return true;
398  }
399  }
400  return false;
401  }
402 
410  public static function ‪isOnCurrentHost(string ‪$url): bool
411  {
412  return stripos(‪$url . '/', self::getIndpEnv('TYPO3_REQUEST_HOST') . '/') === 0;
413  }
414 
423  public static function ‪inList($list, $item)
424  {
425  return str_contains(',' . $list . ',', ',' . $item . ',');
426  }
427 
435  public static function ‪expandList($list)
436  {
437  $items = explode(',', $list);
438  $list = [];
439  foreach ($items as $item) {
440  $range = explode('-', $item);
441  if (isset($range[1])) {
442  $runAwayBrake = 1000;
443  for ($n = $range[0]; $n <= $range[1]; $n++) {
444  $list[] = $n;
445  $runAwayBrake--;
446  if ($runAwayBrake <= 0) {
447  break;
448  }
449  }
450  } else {
451  $list[] = $item;
452  }
453  }
454  return implode(',', $list);
455  }
456 
463  public static function ‪md5int($str)
464  {
465  return hexdec(substr(md5($str), 0, 7));
466  }
467 
475  public static function ‪hmac($input, $additionalSecret = '')
476  {
477  $hashAlgorithm = 'sha1';
478  $secret = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'] . $additionalSecret;
479  return hash_hmac($hashAlgorithm, $input, $secret);
480  }
481 
488  public static function split_fileref($fileNameWithPath)
489  {
490  $info = [];
491  $reg = [];
492  if (preg_match('/(.*\\/)(.*)$/', $fileNameWithPath, $reg)) {
493  $info['path'] = $reg[1];
494  $info['file'] = $reg[2];
495  } else {
496  $info['path'] = '';
497  $info['file'] = $fileNameWithPath;
498  }
499  $reg = '';
500  // If open_basedir is set and the fileName was supplied without a path the is_dir check fails
501  if (!is_dir($fileNameWithPath) && preg_match('/(.*)\\.([^\\.]*$)/', $info['file'], $reg)) {
502  $info['filebody'] = $reg[1];
503  $info['fileext'] = strtolower($reg[2]);
504  $info['realFileext'] = $reg[2];
505  } else {
506  $info['filebody'] = $info['file'];
507  $info['fileext'] = '';
508  }
509  reset($info);
510  return $info;
511  }
512 
528  public static function dirname($path)
529  {
530  $p = self::revExplode('/', $path, 2);
531  return count($p) === 2 ? $p[0] : '';
532  }
533 
542  public static function formatSize(‪$sizeInBytes, $labels = '', $base = 0)
543  {
544  $defaultFormats = [
545  'iec' => ['base' => 1024, 'labels' => [' ', ' Ki', ' Mi', ' Gi', ' Ti', ' Pi', ' Ei', ' Zi', ' Yi']],
546  'si' => ['base' => 1000, 'labels' => [' ', ' k', ' M', ' G', ' T', ' P', ' E', ' Z', ' Y']],
547  ];
548  // Set labels and base:
549  if (empty($labels)) {
550  $labels = 'iec';
551  }
552  if (isset($defaultFormats[$labels])) {
553  $base = $defaultFormats[$labels]['base'];
554  ‪$labelArr = $defaultFormats[$labels]['labels'];
555  } else {
556  $base = (int)$base;
557  if ($base !== 1000 && $base !== 1024) {
558  $base = 1024;
559  }
560  ‪$labelArr = explode('|', str_replace('"', '', $labels));
561  }
562  // This is set via Site Handling and in the Locales class via setlocale()
563  // LC_NUMERIC is not set because of side effects when calculating with floats
564  // see @\TYPO3\CMS\Core\Localization\Locales::setLocale
565  ‪$currentLocale = setlocale(LC_MONETARY, '0');
566  ‪$oldLocale = setlocale(LC_NUMERIC, '0');
567  setlocale(LC_NUMERIC, ‪$currentLocale);
568  ‪$localeInfo = localeconv();
569  setlocale(LC_NUMERIC, ‪$oldLocale);
570 
572  ‪$multiplier = floor((‪$sizeInBytes ? log(‪$sizeInBytes) : 0) / log($base));
574  if (‪$sizeInUnits > ($base * .9)) {
575  ‪$multiplier++;
576  }
579  return number_format(‪$sizeInUnits, ((‪$multiplier > 0) && (‪$sizeInUnits < 20)) ? 2 : 0, ‪$localeInfo['decimal_point'], '') . ‪$labelArr[‪$multiplier];
580  }
581 
591  public static function splitCalc($string, $operators)
592  {
593  $res = [];
594  $sign = '+';
595  while ($string) {
596  $valueLen = strcspn($string, $operators);
597  $value = substr($string, 0, $valueLen);
598  $res[] = [$sign, trim($value)];
599  $sign = substr($string, $valueLen, 1);
600  $string = substr($string, $valueLen + 1);
601  }
602  reset($res);
603  return $res;
604  }
605 
612  public static function validEmail(string $email): bool
613  {
614  if (trim($email) !== $email) {
615  return false;
616  }
617  if (!str_contains($email, '@')) {
618  return false;
619  }
620  $validators = [];
621  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['MAIL']['validators'] ?? [RFCValidation::class] as $className) {
622  ‪$validator = new $className();
623  if (‪$validator instanceof EmailValidation) {
624  $validators[] = ‪$validator;
625  }
626  }
627  return (new EmailValidator())->isValid($email, new MultipleValidationWithAnd($validators, MultipleValidationWithAnd::STOP_ON_ERROR));
628  }
629 
637  public static function ‪underscoredToUpperCamelCase($string)
638  {
639  return str_replace(' ', '', ucwords(str_replace('_', ' ', strtolower($string))));
640  }
641 
649  public static function ‪underscoredToLowerCamelCase($string)
650  {
651  return lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', strtolower($string)))));
652  }
653 
661  public static function ‪camelCaseToLowerCaseUnderscored($string)
662  {
663  $value = preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $string) ?? '';
664  return mb_strtolower($value, 'utf-8');
665  }
666 
691  public static function ‪isValidUrl(string ‪$url): bool
692  {
693  $parsedUrl = parse_url(‪$url);
694  if (!$parsedUrl || !isset($parsedUrl['scheme'])) {
695  return false;
696  }
697  // HttpUtility::buildUrl() will always build urls with <scheme>://
698  // our original $url might only contain <scheme>: (e.g. mail:)
699  // so we convert that to the double-slashed version to ensure
700  // our check against the $recomposedUrl is proper
701  if (!str_starts_with(‪$url, $parsedUrl['scheme'] . '://')) {
702  ‪$url = str_replace($parsedUrl['scheme'] . ':', $parsedUrl['scheme'] . '://', ‪$url);
703  }
704  $recomposedUrl = ‪HttpUtility::buildUrl($parsedUrl);
705  if ($recomposedUrl !== ‪$url) {
706  // The parse_url() had to modify characters, so the URL is invalid
707  return false;
708  }
709  if (isset($parsedUrl['host']) && !preg_match('/^[a-z0-9.\\-]*$/i', $parsedUrl['host'])) {
710  $host = idn_to_ascii($parsedUrl['host']);
711  if ($host === false) {
712  return false;
713  }
714  $parsedUrl['host'] = $host;
715  }
716  return filter_var(‪HttpUtility::buildUrl($parsedUrl), FILTER_VALIDATE_URL) !== false;
717  }
718 
719  /*************************
720  *
721  * ARRAY FUNCTIONS
722  *
723  *************************/
724 
734  public static function intExplode(string $delimiter, string $string, bool $removeEmptyValues = false): array
735  {
736  $result = explode($delimiter, $string);
737  foreach ($result as $key => &$value) {
738  if ($removeEmptyValues && trim($value) === '') {
739  unset($result[$key]);
740  } else {
741  $value = (int)$value;
742  }
743  }
744  unset($value);
745  // @todo: refactor this method to first remove empty values using `array_filter`,
746  // and then `array_walk` with `intval` on the result
748  return $result;
749  }
750 
766  public static function revExplode(string $delimiter, string $string, int $limit = 0): array
767  {
768  // 2 is the (currently, as of 2014-02) most-used value for `$limit` in the core, therefore we check it first
769  if ($limit === 2) {
770  $position = strrpos($string, strrev($delimiter));
771  if ($position !== false) {
772  return [substr($string, 0, $position), substr($string, $position + strlen($delimiter))];
773  }
774  return [$string];
775  }
776  if ($limit <= 1) {
777  return [$string];
778  }
779  $explodedValues = explode($delimiter, strrev($string), $limit);
780  $explodedValues = array_map(strrev(...), $explodedValues);
781  return array_reverse($explodedValues);
782  }
783 
801  public static function trimExplode(string $delim, string $string, bool $removeEmptyValues = false, int $limit = 0): array
802  {
803  $result = explode($delim, $string);
804  if ($removeEmptyValues) {
805  // Remove items that are just whitespace, but leave whitespace intact for the rest.
806  $result = array_values(array_filter($result, static fn (string $item): bool => trim($item) !== ''));
807  }
808 
809  if ($limit === 0) {
810  // Return everything.
811  return array_map(trim(...), $result);
812  }
813 
814  if ($limit < 0) {
815  // Trim and return just the first $limit elements and ignore the rest.
816  return array_map(trim(...), array_slice($result, 0, $limit));
817  }
818 
819  // Fold the last length - $limit elements into a single trailing item, then trim and return the result.
820  $tail = array_slice($result, $limit - 1);
821  $result = array_slice($result, 0, $limit - 1);
822  if ($tail) {
823  $result[] = implode($delim, $tail);
824  }
825  return array_map(trim(...), $result);
826  }
827 
839  public static function implodeArrayForUrl(string $name, array $theArray, string $str = '', bool $skipBlank = false, bool $rawurlencodeParamName = false): string
840  {
841  foreach ($theArray as $Akey => $AVal) {
842  $thisKeyName = $name ? $name . '[' . $Akey . ']' : $Akey;
843  if (is_array($AVal)) {
844  $str = self::implodeArrayForUrl($thisKeyName, $AVal, $str, $skipBlank, $rawurlencodeParamName);
845  } else {
846  $stringValue = (string)$AVal;
847  if (!$skipBlank || $stringValue !== '') {
848  $parameterName = $rawurlencodeParamName ? rawurlencode($thisKeyName) : $thisKeyName;
849  $parameterValue = rawurlencode($stringValue);
850  $str .= '&' . $parameterName . '=' . $parameterValue;
851  }
852  }
853  }
854  return $str;
855  }
856 
872  public static function explodeUrl2Array(string $string): array
873  {
874  ‪$output = [];
875  $p = explode('&', $string);
876  foreach ($p as $v) {
877  if ($v !== '') {
878  $nameAndValue = explode('=', $v, 2);
879  ‪$output[rawurldecode($nameAndValue[0])] = isset($nameAndValue[1]) ? rawurldecode($nameAndValue[1]) : '';
880  }
881  }
882  return ‪$output;
883  }
884 
892  public static function removeDotsFromTS(array $ts): array
893  {
894  $out = [];
895  foreach ($ts as $key => $value) {
896  if (is_array($value)) {
897  $key = rtrim($key, '.');
898  $out[$key] = self::removeDotsFromTS($value);
899  } else {
900  $out[$key] = $value;
901  }
902  }
903  return $out;
904  }
905 
906  /*************************
907  *
908  * HTML/XML PROCESSING
909  *
910  *************************/
920  public static function get_tag_attributes($tag, bool $decodeEntities = false)
921  {
922  $components = self::split_tag_attributes($tag);
923  // Attribute name is stored here
924  $name = '';
925  $valuemode = false;
926  $attributes = [];
927  foreach ($components as $key => $val) {
928  // 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
929  if ($val !== '=') {
930  if ($valuemode) {
931  if ($name) {
932  $attributes[$name] = $decodeEntities ? htmlspecialchars_decode($val) : $val;
933  $name = '';
934  }
935  } else {
936  if ($key = strtolower(preg_replace('/[^[:alnum:]_\\:\\-]/', '', $val) ?? '')) {
937  $attributes[$key] = '';
938  $name = $key;
939  }
940  }
941  $valuemode = false;
942  } else {
943  $valuemode = true;
944  }
945  }
946  return $attributes;
947  }
948 
956  public static function split_tag_attributes($tag)
957  {
958  $tag_tmp = trim(preg_replace('/^<[^[:space:]]*/', '', trim($tag)) ?? '');
959  // Removes any > in the end of the string
960  $tag_tmp = trim(rtrim($tag_tmp, '>'));
961  $value = [];
962  // Compared with empty string instead , 030102
963  while ($tag_tmp !== '') {
964  $firstChar = $tag_tmp[0];
965  if ($firstChar === '"' || $firstChar === '\'') {
966  $reg = explode($firstChar, $tag_tmp, 3);
967  $value[] = $reg[1];
968  $tag_tmp = trim($reg[2] ?? '');
969  } elseif ($firstChar === '=') {
970  $value[] = '=';
971  // Removes = chars.
972  $tag_tmp = trim(substr($tag_tmp, 1));
973  } else {
974  // There are '' around the value. We look for the next ' ' or '>'
975  $reg = preg_split('/[[:space:]=]/', $tag_tmp, 2);
976  $value[] = trim($reg[0]);
977  $tag_tmp = trim(substr($tag_tmp, strlen($reg[0]), 1) . ($reg[1] ?? ''));
978  }
979  }
980  reset($value);
981  return $value;
982  }
983 
992  public static function implodeAttributes(array $arr, $xhtmlSafe = false, $keepBlankAttributes = false)
993  {
994  if ($xhtmlSafe) {
995  $newArr = [];
996  foreach ($arr as $attributeName => $attributeValue) {
997  $attributeName = strtolower($attributeName);
998  if (!isset($newArr[$attributeName])) {
999  $newArr[$attributeName] = htmlspecialchars((string)$attributeValue);
1000  }
1001  }
1002  $arr = $newArr;
1003  }
1004  $list = [];
1005  foreach ($arr as $attributeName => $attributeValue) {
1006  if ((string)$attributeValue !== '' || $keepBlankAttributes) {
1007  $list[] = $attributeName . '="' . $attributeValue . '"';
1008  }
1009  }
1010  return implode(' ', $list);
1011  }
1012 
1022  public static function wrapJS(string $string, array $attributes = [])
1023  {
1024  if (trim($string)) {
1025  // remove nl from the beginning
1026  $string = ltrim($string, LF);
1027  // re-ident to one tab using the first line as reference
1028  $match = [];
1029  if (preg_match('/^(\\t+)/', $string, $match)) {
1030  $string = str_replace($match[1], "\t", $string);
1031  }
1032  return '<script ' . GeneralUtility::implodeAttributes($attributes, true) . '>
1033 /*<![CDATA[*/
1034 ' . $string . '
1035 /*]]>*/
1036 </script>';
1037  }
1038  return '';
1039  }
1040 
1049  public static function xml2tree($string, $depth = 999, $parserOptions = [])
1050  {
1051  ‪$parser = xml_parser_create();
1052  $vals = [];
1053  $index = [];
1054  xml_parser_set_option(‪$parser, XML_OPTION_CASE_FOLDING, 0);
1055  xml_parser_set_option(‪$parser, XML_OPTION_SKIP_WHITE, 0);
1056  foreach ($parserOptions as $option => $value) {
1057  xml_parser_set_option(‪$parser, $option, $value);
1058  }
1059  xml_parse_into_struct(‪$parser, $string, $vals, $index);
1060  if (xml_get_error_code(‪$parser)) {
1061  return 'Line ' . xml_get_current_line_number(‪$parser) . ': ' . xml_error_string(xml_get_error_code(‪$parser));
1062  }
1063  xml_parser_free(‪$parser);
1064  $stack = [[]];
1065  $stacktop = 0;
1066  $startPoint = 0;
1067  $tagi = [];
1068  foreach ($vals as $key => $val) {
1069  $type = $val['type'];
1070  // open tag:
1071  if ($type === 'open' || $type === 'complete') {
1072  $stack[$stacktop++] = $tagi;
1073  if ($depth == $stacktop) {
1074  $startPoint = $key;
1075  }
1076  $tagi = ['tag' => $val['tag']];
1077  if (isset($val['attributes'])) {
1078  $tagi['attrs'] = $val['attributes'];
1079  }
1080  if (isset($val['value'])) {
1081  $tagi['values'][] = $val['value'];
1082  }
1083  }
1084  // finish tag:
1085  if ($type === 'complete' || $type === 'close') {
1086  $oldtagi = $tagi;
1087  $tagi = $stack[--$stacktop];
1088  $oldtag = $oldtagi['tag'];
1089  unset($oldtagi['tag']);
1090  if ($depth == $stacktop + 1) {
1091  if ($key - $startPoint > 0) {
1092  $partArray = array_slice($vals, $startPoint + 1, $key - $startPoint - 1);
1093  $oldtagi['XMLvalue'] = ‪self::xmlRecompileFromStructValArray($partArray);
1094  } else {
1095  $oldtagi['XMLvalue'] = $oldtagi['values'][0];
1096  }
1097  }
1098  $tagi['ch'][$oldtag][] = $oldtagi;
1099  unset($oldtagi);
1100  }
1101  // cdata
1102  if ($type === 'cdata') {
1103  $tagi['values'][] = $val['value'];
1104  }
1105  }
1106  return $tagi['ch'];
1107  }
1108 
1129  public static function array2xml(array $array, $NSprefix = '', $level = 0, $docTag = 'phparray', $spaceInd = 0, array $options = [], array $stackData = [])
1130  {
1131  // 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
1132  $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);
1133  // Set indenting mode:
1134  $indentChar = $spaceInd ? ' ' : "\t";
1135  $indentN = $spaceInd > 0 ? $spaceInd : 1;
1136  $nl = $spaceInd >= 0 ? LF : '';
1137  // Init output variable:
1139  // Traverse the input array
1140  foreach ($array as $k => $v) {
1141  $attr = '';
1142  $tagName = (string)$k;
1143  // Construct the tag name.
1144  // Use tag based on grand-parent + parent tag name
1145  if (isset($stackData['grandParentTagName'], $stackData['parentTagName'], $options['grandParentTagMap'][$stackData['grandParentTagName'] . '/' . $stackData['parentTagName']])) {
1146  $attr .= ' index="' . htmlspecialchars($tagName) . '"';
1147  $tagName = (string)$options['grandParentTagMap'][$stackData['grandParentTagName'] . '/' . $stackData['parentTagName']];
1148  } elseif (isset($stackData['parentTagName'], $options['parentTagMap'][$stackData['parentTagName'] . ':_IS_NUM']) && ‪MathUtility::canBeInterpretedAsInteger($tagName)) {
1149  // Use tag based on parent tag name + if current tag is numeric
1150  $attr .= ' index="' . htmlspecialchars($tagName) . '"';
1151  $tagName = (string)$options['parentTagMap'][$stackData['parentTagName'] . ':_IS_NUM'];
1152  } elseif (isset($stackData['parentTagName'], $options['parentTagMap'][$stackData['parentTagName'] . ':' . $tagName])) {
1153  // Use tag based on parent tag name + current tag
1154  $attr .= ' index="' . htmlspecialchars($tagName) . '"';
1155  $tagName = (string)$options['parentTagMap'][$stackData['parentTagName'] . ':' . $tagName];
1156  } elseif (isset($stackData['parentTagName'], $options['parentTagMap'][$stackData['parentTagName']])) {
1157  // Use tag based on parent tag name:
1158  $attr .= ' index="' . htmlspecialchars($tagName) . '"';
1159  $tagName = (string)$options['parentTagMap'][$stackData['parentTagName']];
1160  } elseif (‪MathUtility::canBeInterpretedAsInteger($tagName)) {
1161  // If integer...;
1162  if ($options['useNindex'] ?? false) {
1163  // If numeric key, prefix "n"
1164  $tagName = 'n' . $tagName;
1165  } else {
1166  // Use special tag for num. keys:
1167  $attr .= ' index="' . $tagName . '"';
1168  $tagName = ($options['useIndexTagForNum'] ?? false) ?: 'numIndex';
1169  }
1170  } elseif (!empty($options['useIndexTagForAssoc'])) {
1171  // Use tag for all associative keys:
1172  $attr .= ' index="' . htmlspecialchars($tagName) . '"';
1173  $tagName = $options['useIndexTagForAssoc'];
1174  }
1175  // The tag name is cleaned up so only alphanumeric chars (plus - and _) are in there and not longer than 100 chars either.
1176  $tagName = substr(preg_replace('/[^[:alnum:]_-]/', '', $tagName), 0, 100);
1177  // If the value is an array then we will call this function recursively:
1178  if (is_array($v)) {
1179  // Sub elements:
1180  if (isset($options['alt_options']) && ($options['alt_options'][($stackData['path'] ?? '') . '/' . $tagName] ?? false)) {
1181  $subOptions = $options['alt_options'][($stackData['path'] ?? '') . '/' . $tagName];
1182  $clearStackPath = (bool)($subOptions['clearStackPath'] ?? false);
1183  } else {
1184  $subOptions = $options;
1185  $clearStackPath = false;
1186  }
1187  if (empty($v)) {
1188  $content = '';
1189  } else {
1190  $content = $nl . self::array2xml($v, $NSprefix, $level + 1, '', $spaceInd, $subOptions, [
1191  'parentTagName' => $tagName,
1192  'grandParentTagName' => $stackData['parentTagName'] ?? '',
1193  'path' => $clearStackPath ? '' : ($stackData['path'] ?? '') . '/' . $tagName,
1194  ]) . ($spaceInd >= 0 ? str_pad('', ($level + 1) * $indentN, $indentChar) : '');
1195  }
1196  // Do not set "type = array". Makes prettier XML but means that empty arrays are not restored with xml2array
1197  if (!isset($options['disableTypeAttrib']) || (int)$options['disableTypeAttrib'] != 2) {
1198  $attr .= ' type="array"';
1199  }
1200  } else {
1201  $stringValue = (string)$v;
1202  // Just a value:
1203  // Look for binary chars:
1204  $vLen = strlen($stringValue);
1205  // Go for base64 encoding if the initial segment NOT matching any binary char has the same length as the whole string!
1206  if ($vLen && strcspn($stringValue, $binaryChars) != $vLen) {
1207  // If the value contained binary chars then we base64-encode it and set an attribute to notify this situation:
1208  $content = $nl . chunk_split(base64_encode($stringValue));
1209  $attr .= ' base64="1"';
1210  } else {
1211  // Otherwise, just htmlspecialchar the stuff:
1212  $content = htmlspecialchars($stringValue);
1213  $dType = gettype($v);
1214  if ($dType === 'string') {
1215  if (isset($options['useCDATA']) && $options['useCDATA'] && $content != $stringValue) {
1216  $content = '<![CDATA[' . $stringValue . ']]>';
1217  }
1218  } elseif (!($options['disableTypeAttrib'] ?? false)) {
1219  $attr .= ' type="' . $dType . '"';
1220  }
1221  }
1222  }
1223  if ($tagName !== '') {
1224  // Add the element to the output string:
1225  ‪$output .= ($spaceInd >= 0 ? str_pad('', ($level + 1) * $indentN, $indentChar) : '')
1226  . '<' . $NSprefix . $tagName . $attr . '>' . $content . '</' . $NSprefix . $tagName . '>' . $nl;
1227  }
1228  }
1229  // If we are at the outer-most level, then we finally wrap it all in the document tags and return that as the value:
1230  if (!$level) {
1231  ‪$output = '<' . $docTag . '>' . $nl . ‪$output . '</' . $docTag . '>';
1232  }
1233  return ‪$output;
1234  }
1235 
1248  public static function ‪xml2array($string, $NSprefix = '', $reportDocTag = false)
1249  {
1250  $runtimeCache = static::makeInstance(CacheManager::class)->getCache('runtime');
1251  $firstLevelCache = $runtimeCache->get('generalUtilityXml2Array') ?: [];
1252  ‪$identifier = md5($string . $NSprefix . ($reportDocTag ? '1' : '0'));
1253  // Look up in first level cache
1254  if (empty($firstLevelCache[‪$identifier])) {
1255  $firstLevelCache[‪$identifier] = ‪self::xml2arrayProcess($string, $NSprefix, $reportDocTag);
1256  $runtimeCache->set('generalUtilityXml2Array', $firstLevelCache);
1257  }
1258  return $firstLevelCache[‪$identifier];
1259  }
1260 
1271  public static function ‪xml2arrayProcess($string, $NSprefix = '', $reportDocTag = false)
1272  {
1273  $string = trim((string)$string);
1274  // Create parser:
1275  ‪$parser = xml_parser_create();
1276  $vals = [];
1277  $index = [];
1278  xml_parser_set_option(‪$parser, XML_OPTION_CASE_FOLDING, 0);
1279  xml_parser_set_option(‪$parser, XML_OPTION_SKIP_WHITE, 0);
1280  // Default output charset is UTF-8, only ASCII, ISO-8859-1 and UTF-8 are supported!!!
1281  $match = [];
1282  preg_match('/^[[:space:]]*<\\?xml[^>]*encoding[[:space:]]*=[[:space:]]*"([^"]*)"/', substr($string, 0, 200), $match);
1283  $theCharset = $match[1] ?? 'utf-8';
1284  // us-ascii / utf-8 / iso-8859-1
1285  xml_parser_set_option(‪$parser, XML_OPTION_TARGET_ENCODING, $theCharset);
1286  // Parse content:
1287  xml_parse_into_struct(‪$parser, $string, $vals, $index);
1288  // If error, return error message:
1289  if (xml_get_error_code(‪$parser)) {
1290  return 'Line ' . xml_get_current_line_number(‪$parser) . ': ' . xml_error_string(xml_get_error_code(‪$parser));
1291  }
1292  xml_parser_free(‪$parser);
1293  // Init vars:
1294  $stack = [[]];
1295  $stacktop = 0;
1296  $current = [];
1297  $tagName = '';
1298  $documentTag = '';
1299  // Traverse the parsed XML structure:
1300  foreach ($vals as $key => $val) {
1301  // First, process the tag-name (which is used in both cases, whether "complete" or "close")
1302  $tagName = $val['tag'];
1303  if (!$documentTag) {
1304  $documentTag = $tagName;
1305  }
1306  // Test for name space:
1307  $tagName = $NSprefix && str_starts_with($tagName, $NSprefix) ? substr($tagName, strlen($NSprefix)) : $tagName;
1308  // Test for numeric tag, encoded on the form "nXXX":
1309  $testNtag = substr($tagName, 1);
1310  // Closing tag.
1311  $tagName = $tagName[0] === 'n' && ‪MathUtility::canBeInterpretedAsInteger($testNtag) ? (int)$testNtag : $tagName;
1312  // Test for alternative index value:
1313  if ((string)($val['attributes']['index'] ?? '') !== '') {
1314  $tagName = $val['attributes']['index'];
1315  }
1316  // Setting tag-values, manage stack:
1317  switch ($val['type']) {
1318  case 'open':
1319  // If open tag it means there is an array stored in sub-elements. Therefore increase the stackpointer and reset the accumulation array:
1320  // Setting blank place holder
1321  $current[$tagName] = [];
1322  $stack[$stacktop++] = $current;
1323  $current = [];
1324  break;
1325  case 'close':
1326  // If the tag is "close" then it is an array which is closing and we decrease the stack pointer.
1327  $oldCurrent = $current;
1328  $current = $stack[--$stacktop];
1329  // Going to the end of array to get placeholder key, key($current), and fill in array next:
1330  end($current);
1331  $current[key($current)] = $oldCurrent;
1332  unset($oldCurrent);
1333  break;
1334  case 'complete':
1335  // If "complete", then it's a value. If the attribute "base64" is set, then decode the value, otherwise just set it.
1336  if (!empty($val['attributes']['base64'])) {
1337  $current[$tagName] = base64_decode($val['value']);
1338  } else {
1339  // Had to cast it as a string - otherwise it would be evaluate FALSE if tested with isset()!!
1340  $current[$tagName] = (string)($val['value'] ?? '');
1341  // Cast type:
1342  switch ((string)($val['attributes']['type'] ?? '')) {
1343  case 'integer':
1344  $current[$tagName] = (int)$current[$tagName];
1345  break;
1346  case 'double':
1347  $current[$tagName] = (float)$current[$tagName];
1348  break;
1349  case 'boolean':
1350  $current[$tagName] = (bool)$current[$tagName];
1351  break;
1352  case 'NULL':
1353  $current[$tagName] = null;
1354  break;
1355  case 'array':
1356  // 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...
1357  $current[$tagName] = [];
1358  break;
1359  }
1360  }
1361  break;
1362  }
1363  }
1364  if ($reportDocTag) {
1365  $current[$tagName]['_DOCUMENT_TAG'] = $documentTag;
1366  }
1367  // Finally return the content of the document tag.
1368  return $current[$tagName];
1369  }
1370 
1377  public static function ‪xmlRecompileFromStructValArray(array $vals)
1378  {
1379  $XMLcontent = '';
1380  foreach ($vals as $val) {
1381  $type = $val['type'];
1382  // Open tag:
1383  if ($type === 'open' || $type === 'complete') {
1384  $XMLcontent .= '<' . $val['tag'];
1385  if (isset($val['attributes'])) {
1386  foreach ($val['attributes'] as $k => $v) {
1387  $XMLcontent .= ' ' . $k . '="' . htmlspecialchars($v) . '"';
1388  }
1389  }
1390  if ($type === 'complete') {
1391  if (isset($val['value'])) {
1392  $XMLcontent .= '>' . htmlspecialchars($val['value']) . '</' . $val['tag'] . '>';
1393  } else {
1394  $XMLcontent .= '/>';
1395  }
1396  } else {
1397  $XMLcontent .= '>';
1398  }
1399  if ($type === 'open' && isset($val['value'])) {
1400  $XMLcontent .= htmlspecialchars($val['value']);
1401  }
1402  }
1403  // Finish tag:
1404  if ($type === 'close') {
1405  $XMLcontent .= '</' . $val['tag'] . '>';
1406  }
1407  // Cdata
1408  if ($type === 'cdata') {
1409  $XMLcontent .= htmlspecialchars($val['value']);
1410  }
1411  }
1412  return $XMLcontent;
1413  }
1414 
1415  /*************************
1416  *
1417  * FILES FUNCTIONS
1418  *
1419  *************************/
1427  public static function ‪getUrl(string ‪$url): string|false
1428  {
1429  // Looks like it's an external file, use Guzzle by default
1430  if (preg_match('/^(?:http|ftp)s?|s(?:ftp|cp):/', ‪$url)) {
1431  $requestFactory = static::makeInstance(RequestFactory::class);
1432  try {
1433  $response = $requestFactory->request(‪$url);
1434  } catch (RequestException $exception) {
1435  return false;
1436  }
1437  $content = $response->getBody()->getContents();
1438  } else {
1439  $content = @file_get_contents(‪$url);
1440  }
1441  return $content;
1442  }
1443 
1452  public static function ‪writeFile($file, $content, $changePermissions = false)
1453  {
1454  if (!@is_file($file)) {
1455  $changePermissions = true;
1456  }
1457  if ($fd = fopen($file, 'wb')) {
1458  $res = fwrite($fd, $content);
1459  fclose($fd);
1460  if ($res === false) {
1461  return false;
1462  }
1463  // Change the permissions only if the file has just been created
1464  if ($changePermissions) {
1465  static::fixPermissions($file);
1466  }
1467  return true;
1468  }
1469  return false;
1470  }
1471 
1479  public static function ‪fixPermissions($path, $recursive = false)
1480  {
1481  $targetPermissions = null;
1482  if (‪Environment::isWindows()) {
1483  return true;
1484  }
1485  $result = false;
1486  // Make path absolute
1487  if (!‪PathUtility::isAbsolutePath($path)) {
1488  $path = static::getFileAbsFileName($path);
1489  }
1490  if (static::isAllowedAbsPath($path)) {
1491  if (@is_file($path)) {
1492  $targetPermissions = (string)(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['fileCreateMask'] ?? '0644');
1493  } elseif (@is_dir($path)) {
1494  $targetPermissions = (string)(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask'] ?? '0755');
1495  }
1496  if (!empty($targetPermissions)) {
1497  // make sure it's always 4 digits
1498  $targetPermissions = str_pad($targetPermissions, 4, '0', STR_PAD_LEFT);
1499  $targetPermissions = octdec($targetPermissions);
1500  // "@" is there because file is not necessarily OWNED by the user
1501  $result = @chmod($path, (int)$targetPermissions);
1502  }
1503  // Set createGroup if not empty
1504  if (
1505  isset(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['createGroup'])
1506  && ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['createGroup'] !== ''
1507  ) {
1508  // "@" is there because file is not necessarily OWNED by the user
1509  $changeGroupResult = @chgrp($path, ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['createGroup']);
1510  $result = $changeGroupResult ? $result : false;
1511  }
1512  // Call recursive if recursive flag if set and $path is directory
1513  if ($recursive && @is_dir($path)) {
1514  $handle = opendir($path);
1515  if (is_resource($handle)) {
1516  while (($file = readdir($handle)) !== false) {
1517  $recursionResult = null;
1518  if ($file !== '.' && $file !== '..') {
1519  if (@is_file($path . '/' . $file)) {
1520  $recursionResult = static::fixPermissions($path . '/' . $file);
1521  } elseif (@is_dir($path . '/' . $file)) {
1522  $recursionResult = static::fixPermissions($path . '/' . $file, true);
1523  }
1524  if (isset($recursionResult) && !$recursionResult) {
1525  $result = false;
1526  }
1527  }
1528  }
1529  closedir($handle);
1530  }
1531  }
1532  }
1533  return $result;
1534  }
1535 
1544  public static function ‪writeFileToTypo3tempDir($filepath, $content)
1545  {
1546  // Parse filepath into directory and basename:
1547  $fI = pathinfo($filepath);
1548  $fI['dirname'] .= '/';
1549  // Check parts:
1550  if (!static::validPathStr($filepath) || !$fI['basename'] || strlen($fI['basename']) >= 60) {
1551  return 'Input filepath "' . $filepath . '" was generally invalid!';
1552  }
1553 
1554  // Setting main temporary directory name (standard)
1555  $allowedPathPrefixes = [
1556  ‪Environment::getPublicPath() . '/typo3temp' => 'Environment::getPublicPath() + "/typo3temp/"',
1557  ];
1558  // Also allow project-path + /var/
1559  if (‪Environment::getVarPath() !== ‪Environment::getPublicPath() . '/typo3temp/var') {
1560  $relPath = substr(‪Environment::getVarPath(), strlen(‪Environment::getProjectPath()) + 1);
1561  $allowedPathPrefixes[‪Environment::getVarPath()] = 'ProjectPath + ' . $relPath;
1562  }
1563 
1564  $errorMessage = null;
1565  foreach ($allowedPathPrefixes as $pathPrefix => $prefixLabel) {
1566  $dirName = $pathPrefix . '/';
1567  // Invalid file path, let's check for the other path, if it exists
1568  if (!str_starts_with($fI['dirname'], $dirName)) {
1569  if ($errorMessage === null) {
1570  $errorMessage = '"' . $fI['dirname'] . '" was not within directory ' . $prefixLabel;
1571  }
1572  continue;
1573  }
1574  // This resets previous error messages from the first path
1575  $errorMessage = null;
1576 
1577  if (!@is_dir($dirName)) {
1578  $errorMessage = $prefixLabel . ' was not a directory!';
1579  // continue and see if the next iteration resets the errorMessage above
1580  continue;
1581  }
1582  // Checking if the "subdir" is found
1583  $subdir = substr($fI['dirname'], strlen($dirName));
1584  if ($subdir) {
1585  if (preg_match('#^(?:[[:alnum:]_]+/)+$#', $subdir)) {
1586  $dirName .= $subdir;
1587  if (!@is_dir($dirName)) {
1588  static::mkdir_deep($pathPrefix . '/' . $subdir);
1589  }
1590  } else {
1591  $errorMessage = 'Subdir, "' . $subdir . '", was NOT on the form "[[:alnum:]_]/+"';
1592  break;
1593  }
1594  }
1595  // Checking dir-name again (sub-dir might have been created)
1596  if (@is_dir($dirName)) {
1597  if ($filepath === $dirName . $fI['basename']) {
1598  static::writeFile($filepath, $content);
1599  if (!@is_file($filepath)) {
1600  $errorMessage = 'The file was not written to the disk. Please, check that you have write permissions to the ' . $prefixLabel . ' directory.';
1601  }
1602  break;
1603  }
1604  $errorMessage = 'Calculated file location didn\'t match input "' . $filepath . '".';
1605  break;
1606  }
1607  $errorMessage = '"' . $dirName . '" is not a directory!';
1608  break;
1609  }
1610  return $errorMessage;
1611  }
1612 
1621  public static function ‪mkdir($newFolder)
1622  {
1623  $result = @‪mkdir($newFolder, (int)octdec((string)(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask'] ?? '0')));
1624  if ($result) {
1625  static::fixPermissions($newFolder);
1626  }
1627  return $result;
1628  }
1629 
1638  public static function ‪mkdir_deep($directory)
1639  {
1640  if (!is_string($directory)) {
1641  throw new \InvalidArgumentException('The specified directory is of type "' . gettype($directory) . '" but a string is expected.', 1303662955);
1642  }
1643  // Ensure there is only one slash
1644  $fullPath = rtrim($directory, '/') . '/';
1645  if ($fullPath !== '/' && !is_dir($fullPath)) {
1646  $firstCreatedPath = static::createDirectoryPath($fullPath);
1647  if ($firstCreatedPath !== '') {
1648  static::fixPermissions($firstCreatedPath, true);
1649  }
1650  }
1651  }
1652 
1664  protected static function ‪createDirectoryPath($fullDirectoryPath)
1665  {
1666  $currentPath = $fullDirectoryPath;
1667  $firstCreatedPath = '';
1668  $permissionMask = (int)octdec((string)(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask'] ?? '0'));
1669  if (!@is_dir($currentPath)) {
1670  do {
1671  $firstCreatedPath = $currentPath;
1672  $separatorPosition = (int)strrpos($currentPath, DIRECTORY_SEPARATOR);
1673  $currentPath = substr($currentPath, 0, $separatorPosition);
1674  } while (!is_dir($currentPath) && $separatorPosition > 0);
1675  $result = @‪mkdir($fullDirectoryPath, $permissionMask, true);
1676  // Check existence of directory again to avoid race condition. Directory could have get created by another process between previous is_dir() and mkdir()
1677  if (!$result && !@is_dir($fullDirectoryPath)) {
1678  throw new \RuntimeException('Could not create directory "' . $fullDirectoryPath . '"!', 1170251401);
1679  }
1680  }
1681  return $firstCreatedPath;
1682  }
1683 
1691  public static function ‪rmdir($path, $removeNonEmpty = false)
1692  {
1693  $OK = false;
1694  // Remove trailing slash
1695  $path = preg_replace('|/$|', '', $path) ?? '';
1696  $isWindows = DIRECTORY_SEPARATOR === '\\';
1697  if (file_exists($path)) {
1698  $OK = true;
1699  if (!is_link($path) && is_dir($path)) {
1700  if ($removeNonEmpty === true && ($handle = @opendir($path))) {
1701  $entries = [];
1702 
1703  while (false !== ($file = readdir($handle))) {
1704  if ($file === '.' || $file === '..') {
1705  continue;
1706  }
1707 
1708  $entries[] = $path . '/' . $file;
1709  }
1710 
1711  closedir($handle);
1712 
1713  foreach ($entries as $entry) {
1714  if (!static::rmdir($entry, $removeNonEmpty)) {
1715  $OK = false;
1716  }
1717  }
1718  }
1719  if ($OK) {
1720  $OK = @‪rmdir($path);
1721  }
1722  } elseif (is_link($path) && is_dir($path) && $isWindows) {
1723  $OK = @‪rmdir($path);
1724  } else {
1725  // If $path is a file, simply remove it
1726  $OK = @unlink($path);
1727  }
1728  clearstatcache();
1729  } elseif (is_link($path)) {
1730  $OK = @unlink($path);
1731  if (!$OK && $isWindows) {
1732  // Try to delete dead folder links on Windows systems
1733  $OK = @‪rmdir($path);
1734  }
1735  clearstatcache();
1736  }
1737  return $OK;
1738  }
1739 
1748  public static function ‪get_dirs($path)
1749  {
1750  $dirs = null;
1751  if ($path) {
1752  if (is_dir($path)) {
1753  ‪$dir = scandir($path);
1754  $dirs = [];
1755  foreach (‪$dir as $entry) {
1756  if (is_dir($path . '/' . $entry) && $entry !== '..' && $entry !== '.') {
1757  $dirs[] = $entry;
1758  }
1759  }
1760  } else {
1761  $dirs = 'error';
1762  }
1763  }
1764  return $dirs;
1765  }
1766 
1779  public static function getFilesInDir($path, $extensionList = '', $prependPath = false, $order = '', $excludePattern = '')
1780  {
1781  $excludePattern = (string)$excludePattern;
1782  $path = rtrim($path, '/');
1783  if (!@is_dir($path)) {
1784  return [];
1785  }
1786 
1787  $rawFileList = scandir($path);
1788  if ($rawFileList === false) {
1789  return 'error opening path: "' . $path . '"';
1790  }
1791 
1792  $pathPrefix = $path . '/';
1793  $allowedFileExtensionArray = self::trimExplode(',', $extensionList);
1794  $extensionList = ',' . str_replace(' ', '', $extensionList) . ',';
1795  $files = [];
1796  foreach ($rawFileList as $entry) {
1797  $completePathToEntry = $pathPrefix . $entry;
1798  if (!@is_file($completePathToEntry)) {
1799  continue;
1800  }
1801 
1802  foreach ($allowedFileExtensionArray as $allowedFileExtension) {
1803  if (
1804  ($extensionList === ',,' || str_ends_with(mb_strtolower($entry), mb_strtolower('.' . $allowedFileExtension)))
1805  && ($excludePattern === '' || !preg_match('/^' . $excludePattern . '$/', $entry))
1806  ) {
1807  if ($order !== 'mtime') {
1808  $files[] = $entry;
1809  } else {
1810  // Store the value in the key so we can do a fast asort later.
1811  $files[$entry] = filemtime($completePathToEntry);
1812  }
1813  }
1814  }
1815  }
1816 
1817  $valueName = 'value';
1818  if ($order === 'mtime') {
1819  asort($files);
1820  $valueName = 'key';
1821  }
1822 
1823  $valuePathPrefix = $prependPath ? $pathPrefix : '';
1824  $foundFiles = [];
1825  foreach ($files as $key => $value) {
1826  // Don't change this ever - extensions may depend on the fact that the hash is an md5 of the path! (import/export extension)
1827  $foundFiles[md5($pathPrefix . ${$valueName})] = $valuePathPrefix . ${$valueName};
1828  }
1829 
1830  return $foundFiles;
1831  }
1832 
1844  public static function getAllFilesAndFoldersInPath(array $fileArr, $path, $extList = '', $regDirs = false, $recursivityLevels = 99, $excludePattern = '')
1845  {
1846  if ($regDirs) {
1847  $fileArr[md5($path)] = $path;
1848  }
1849  $fileArr = array_merge($fileArr, (array)self::getFilesInDir($path, $extList, true, '', $excludePattern));
1850  $dirs = ‪self::get_dirs($path);
1851  if ($recursivityLevels > 0 && is_array($dirs)) {
1852  foreach ($dirs as $subdirs) {
1853  if ((string)$subdirs !== '' && ($excludePattern === '' || !preg_match('/^' . $excludePattern . '$/', $subdirs))) {
1854  $fileArr = self::getAllFilesAndFoldersInPath($fileArr, $path . $subdirs . '/', $extList, $regDirs, $recursivityLevels - 1, $excludePattern);
1855  }
1856  }
1857  }
1858  return $fileArr;
1859  }
1860 
1868  public static function removePrefixPathFromList(array $fileArr, string $prefixToRemove)
1869  {
1870  foreach ($fileArr as &$absFileRef) {
1871  if (str_starts_with($absFileRef, $prefixToRemove)) {
1872  $absFileRef = substr($absFileRef, strlen($prefixToRemove));
1873  } else {
1874  return 'ERROR: One or more of the files was NOT prefixed with the prefix-path!';
1875  }
1876  }
1877  unset($absFileRef);
1878  return $fileArr;
1879  }
1880 
1884  public static function fixWindowsFilePath(string $theFile): string
1885  {
1886  return str_replace(['\\', '//'], '/', $theFile);
1887  }
1888 
1896  public static function resolveBackPath($pathStr)
1897  {
1898  if (!str_contains($pathStr, '..')) {
1899  return $pathStr;
1900  }
1901  $parts = explode('/', $pathStr);
1902  ‪$output = [];
1903  $c = 0;
1904  foreach ($parts as $part) {
1905  if ($part === '..') {
1906  if ($c) {
1907  array_pop(‪$output);
1908  --$c;
1909  } else {
1910  ‪$output[] = $part;
1911  }
1912  } else {
1913  ++$c;
1914  ‪$output[] = $part;
1915  }
1916  }
1917  return implode('/', ‪$output);
1918  }
1919 
1929  public static function locationHeaderUrl(string $path): string
1930  {
1931  if (str_starts_with($path, '//')) {
1932  return $path;
1933  }
1934 
1935  // relative to HOST
1936  if (str_starts_with($path, '/')) {
1937  return self::getIndpEnv('TYPO3_REQUEST_HOST') . $path;
1938  }
1939 
1940  $urlComponents = parse_url($path);
1941  if (!($urlComponents['scheme'] ?? false)) {
1942  // No scheme either
1943  return self::getIndpEnv('TYPO3_REQUEST_DIR') . $path;
1944  }
1945 
1946  return $path;
1947  }
1948 
1956  public static function getMaxUploadFileSize(): int
1957  {
1958  $uploadMaxFilesize = (string)ini_get('upload_max_filesize');
1959  $postMaxSize = (string)ini_get('post_max_size');
1960  // Check for PHP restrictions of the maximum size of one of the $_FILES
1961  $phpUploadLimit = self::getBytesFromSizeMeasurement($uploadMaxFilesize);
1962  // Check for PHP restrictions of the maximum $_POST size
1963  $phpPostLimit = self::getBytesFromSizeMeasurement($postMaxSize);
1964  // If the total amount of post data is smaller (!) than the upload_max_filesize directive,
1965  // then this is the real limit in PHP
1966  $phpUploadLimit = $phpPostLimit > 0 && $phpPostLimit < $phpUploadLimit ? $phpPostLimit : $phpUploadLimit;
1967  return (int)(floor($phpUploadLimit) / 1024);
1968  }
1969 
1976  public static function getBytesFromSizeMeasurement($measurement)
1977  {
1978  $bytes = (float)$measurement;
1979  if (stripos($measurement, 'G')) {
1980  $bytes *= 1024 * 1024 * 1024;
1981  } elseif (stripos($measurement, 'M')) {
1982  $bytes *= 1024 * 1024;
1983  } elseif (stripos($measurement, 'K')) {
1984  $bytes *= 1024;
1985  }
1986  return (int)$bytes;
1987  }
1988 
2005  public static function createVersionNumberedFilename($file): string
2006  {
2007  $isFrontend = (‪$GLOBALS['TYPO3_REQUEST'] ?? null) instanceof ServerRequestInterface
2008  && ‪ApplicationType::fromRequest(‪$GLOBALS['TYPO3_REQUEST'])->isFrontend();
2009  $lookupFile = explode('?', $file);
2010  $path = $lookupFile[0];
2011 
2012  // @todo: in v13 this should be resolved by using Environment::getPublicPath() only
2013  if ($isFrontend) {
2014  // Frontend should still allow /static/myfile.css - see #98106
2015  // This should happen regardless of the incoming path is absolute or not
2016  $path = self::resolveBackPath(self::dirname(‪Environment::getCurrentScript()) . '/' . $path);
2017  } elseif (!‪PathUtility::isAbsolutePath($path)) {
2018  // Backend and non-absolute path
2019  $path = self::resolveBackPath(self::dirname(‪Environment::getCurrentScript()) . '/' . $path);
2020  }
2021 
2022  if ($isFrontend) {
2023  $configValue = (bool)(‪$GLOBALS['TYPO3_CONF_VARS']['FE']['versionNumberInFilename'] ?? false);
2024  } else {
2025  $configValue = (bool)(‪$GLOBALS['TYPO3_CONF_VARS']['BE']['versionNumberInFilename'] ?? false);
2026  }
2027  try {
2028  $fileExists = file_exists($path);
2029  } catch (\Throwable $e) {
2030  $fileExists = false;
2031  }
2032  if (!$fileExists) {
2033  // File not found, return filename unaltered
2034  $fullName = $file;
2035  } else {
2036  if (!$configValue) {
2037  // If .htaccess rule is not configured,
2038  // use the default query-string method
2039  if (!empty($lookupFile[1])) {
2040  $separator = '&';
2041  } else {
2042  $separator = '?';
2043  }
2044  $fullName = $file . $separator . filemtime($path);
2045  } else {
2046  // Change the filename
2047  $name = explode('.', $lookupFile[0]);
2048  $extension = array_pop($name);
2049  array_push($name, filemtime($path), $extension);
2050  $fullName = implode('.', $name);
2051  // Append potential query string
2052  $fullName .= !empty($lookupFile[1]) ? '?' . $lookupFile[1] : '';
2053  }
2054  }
2055  return $fullName;
2056  }
2057 
2065  public static function writeJavaScriptContentToTemporaryFile(string $content)
2066  {
2067  $script = 'typo3temp/assets/js/' . md5($content) . '.js';
2068  if (!@is_file(‪Environment::getPublicPath() . '/' . $script)) {
2070  }
2071  return $script;
2072  }
2073 
2081  public static function writeStyleSheetContentToTemporaryFile(string $content)
2082  {
2083  $script = 'typo3temp/assets/css/' . md5($content) . '.css';
2084  if (!@is_file(‪Environment::getPublicPath() . '/' . $script)) {
2086  }
2087  return $script;
2088  }
2089 
2097  public static function setIndpEnv($envName, $value)
2098  {
2099  self::$indpEnvCache[$envName] = $value;
2100  }
2101 
2110  public static function getIndpEnv($getEnvName)
2111  {
2112  if (array_key_exists($getEnvName, self::$indpEnvCache)) {
2113  return self::$indpEnvCache[$getEnvName];
2114  }
2115 
2116  /*
2117  Conventions:
2118  output from parse_url():
2119  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
2120  [scheme] => 'http'
2121  [user] => 'username'
2122  [pass] => 'password'
2123  [host] => '192.168.1.4'
2124  [port] => '8080'
2125  [path] => '/typo3/32/temp/phpcheck/index.php/arg1/arg2/arg3/'
2126  [query] => 'arg1,arg2,arg3&p1=parameter1&p2[key]=value'
2127  [fragment] => 'link1'Further definition: [path_script] = '/typo3/32/temp/phpcheck/index.php'
2128  [path_dir] = '/typo3/32/temp/phpcheck/'
2129  [path_info] = '/arg1/arg2/arg3/'
2130  [path] = [path_script/path_dir][path_info]Keys supported:URI______:
2131  REQUEST_URI = [path]?[query] = /typo3/32/temp/phpcheck/index.php/arg1/arg2/arg3/?arg1,arg2,arg3&p1=parameter1&p2[key]=value
2132  HTTP_HOST = [host][:[port]] = 192.168.1.4:8080
2133  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')!
2134  PATH_INFO = [path_info] = /arg1/arg2/arg3/
2135  QUERY_STRING = [query] = arg1,arg2,arg3&p1=parameter1&p2[key]=value
2136  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
2137  (Notice: NO username/password + NO fragment)CLIENT____:
2138  REMOTE_ADDR = (client IP)
2139  REMOTE_HOST = (client host)
2140  HTTP_USER_AGENT = (client user agent)
2141  HTTP_ACCEPT_LANGUAGE = (client accept language)SERVER____:
2142  SCRIPT_FILENAME = Absolute filename of script (Differs between windows/unix). On windows 'C:\\some\\path\\' will be converted to 'C:/some/path/'Special extras:
2143  TYPO3_HOST_ONLY = [host] = 192.168.1.4
2144  TYPO3_PORT = [port] = 8080 (blank if 80, taken from host value)
2145  TYPO3_REQUEST_HOST = [scheme]://[host][:[port]]
2146  TYPO3_REQUEST_URL = [scheme]://[host][:[port]][path]?[query] (scheme will by default be "http" until we can detect something different)
2147  TYPO3_REQUEST_SCRIPT = [scheme]://[host][:[port]][path_script]
2148  TYPO3_REQUEST_DIR = [scheme]://[host][:[port]][path_dir]
2149  TYPO3_SITE_URL = [scheme]://[host][:[port]][path_dir] of the TYPO3 website frontend
2150  TYPO3_SITE_PATH = [path_dir] of the TYPO3 website frontend
2151  TYPO3_SITE_SCRIPT = [script / Speaking URL] of the TYPO3 website
2152  TYPO3_DOCUMENT_ROOT = Absolute path of root of documents: TYPO3_DOCUMENT_ROOT.SCRIPT_NAME = SCRIPT_FILENAME (typically)
2153  TYPO3_SSL = Returns TRUE if this session uses SSL/TLS (https)
2154  TYPO3_PROXY = Returns TRUE if this session runs over a well known proxyNotice: [fragment] is apparently NEVER available to the script!Testing suggestions:
2155  - Output all the values.
2156  - 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
2157  - ALSO TRY the script from the ROOT of a site (like 'http://www.mytest.com/' and not 'http://www.mytest.com/test/' !!)
2158  */
2159  $retVal = '';
2160  switch ((string)$getEnvName) {
2161  case 'SCRIPT_NAME':
2162  $retVal = $_SERVER['SCRIPT_NAME'] ?? '';
2163  // Add a prefix if TYPO3 is behind a proxy: ext-domain.com => int-server.com/prefix
2164  if (self::cmpIP($_SERVER['REMOTE_ADDR'] ?? '', ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyIP'] ?? '')) {
2165  if (self::getIndpEnv('TYPO3_SSL') && ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyPrefixSSL']) {
2166  $retVal = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyPrefixSSL'] . $retVal;
2167  } elseif (‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyPrefix']) {
2168  $retVal = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyPrefix'] . $retVal;
2169  }
2170  }
2171  $retVal = self::encodeFileSystemPathComponentForUrlPath($retVal);
2172  break;
2173  case 'SCRIPT_FILENAME':
2175  break;
2176  case 'REQUEST_URI':
2177  // Typical application of REQUEST_URI is return urls, forms submitting to itself etc. Example: returnUrl='.rawurlencode(\TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('REQUEST_URI'))
2178  if (!empty(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['requestURIvar'])) {
2179  // This is for URL rewriters that store the original URI in a server variable (eg ISAPI_Rewriter for IIS: HTTP_X_REWRITE_URL)
2180  [$v, $n] = explode('|', ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['requestURIvar']);
2181  $retVal = ‪$GLOBALS[$v][$n];
2182  } elseif (empty($_SERVER['REQUEST_URI'])) {
2183  // This is for ISS/CGI which does not have the REQUEST_URI available.
2184  $retVal = '/' . ltrim(self::getIndpEnv('SCRIPT_NAME'), '/') . (!empty($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : '');
2185  } else {
2186  $retVal = '/' . ltrim($_SERVER['REQUEST_URI'], '/');
2187  }
2188  // Add a prefix if TYPO3 is behind a proxy: ext-domain.com => int-server.com/prefix
2189  if (isset($_SERVER['REMOTE_ADDR'], ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyIP'])
2190  && self::cmpIP($_SERVER['REMOTE_ADDR'], ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyIP'])
2191  ) {
2192  if (self::getIndpEnv('TYPO3_SSL') && ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyPrefixSSL']) {
2193  $retVal = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyPrefixSSL'] . $retVal;
2194  } elseif (‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyPrefix']) {
2195  $retVal = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyPrefix'] . $retVal;
2196  }
2197  }
2198  break;
2199  case 'PATH_INFO':
2200  $retVal = $_SERVER['PATH_INFO'] ?? '';
2201  break;
2202  case 'TYPO3_REV_PROXY':
2203  $retVal = ‪self::cmpIP($_SERVER['REMOTE_ADDR'] ?? '', ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyIP']);
2204  break;
2205  case 'REMOTE_ADDR':
2206  $retVal = $_SERVER['REMOTE_ADDR'] ?? '';
2207  if (self::cmpIP($retVal, ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyIP'] ?? '')) {
2208  $ip = self::trimExplode(',', $_SERVER['HTTP_X_FORWARDED_FOR'] ?? '');
2209  // Choose which IP in list to use
2210  if (!empty($ip)) {
2211  switch (‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyHeaderMultiValue']) {
2212  case 'last':
2213  $ip = array_pop($ip);
2214  break;
2215  case 'first':
2216  $ip = array_shift($ip);
2217  break;
2218  case 'none':
2219 
2220  default:
2221  $ip = '';
2222  }
2223  }
2224  if (self::validIP((string)$ip)) {
2225  $retVal = $ip;
2226  }
2227  }
2228  break;
2229  case 'HTTP_HOST':
2230  // if it is not set we're most likely on the cli
2231  $retVal = $_SERVER['HTTP_HOST'] ?? '';
2232  if (isset($_SERVER['REMOTE_ADDR']) && static::cmpIP($_SERVER['REMOTE_ADDR'], ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyIP'])) {
2233  $host = self::trimExplode(',', $_SERVER['HTTP_X_FORWARDED_HOST'] ?? '');
2234  // Choose which host in list to use
2235  if (!empty($host)) {
2236  switch (‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyHeaderMultiValue']) {
2237  case 'last':
2238  $host = array_pop($host);
2239  break;
2240  case 'first':
2241  $host = array_shift($host);
2242  break;
2243  case 'none':
2244 
2245  default:
2246  $host = '';
2247  }
2248  }
2249  if ($host) {
2250  $retVal = $host;
2251  }
2252  }
2253  break;
2254  case 'HTTP_REFERER':
2255 
2256  case 'HTTP_USER_AGENT':
2257 
2258  case 'HTTP_ACCEPT_ENCODING':
2259 
2260  case 'HTTP_ACCEPT_LANGUAGE':
2261 
2262  case 'REMOTE_HOST':
2263 
2264  case 'QUERY_STRING':
2265  $retVal = $_SERVER[$getEnvName] ?? '';
2266  break;
2267  case 'TYPO3_DOCUMENT_ROOT':
2268  // Get the web root (it is not the root of the TYPO3 installation)
2269  // The absolute path of the script can be calculated with TYPO3_DOCUMENT_ROOT + SCRIPT_FILENAME
2270  // 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.
2271  // Therefore the DOCUMENT_ROOT is now always calculated as the SCRIPT_FILENAME minus the end part shared with SCRIPT_NAME.
2272  $SFN = self::getIndpEnv('SCRIPT_FILENAME');
2273  // Use rawurldecode to reverse the result of self::encodeFileSystemPathComponentForUrlPath()
2274  // which has been applied to getIndpEnv(SCRIPT_NAME) for web URI usage.
2275  // We compare with a file system path (SCRIPT_FILENAME) in here and therefore need to undo the encoding.
2276  $SN_A = array_map(rawurldecode(...), explode('/', strrev(self::getIndpEnv('SCRIPT_NAME'))));
2277  $SFN_A = explode('/', strrev($SFN));
2278  $acc = [];
2279  foreach ($SN_A as $kk => $vv) {
2280  if ((string)$SFN_A[$kk] === (string)$vv) {
2281  $acc[] = $vv;
2282  } else {
2283  break;
2284  }
2285  }
2286  $commonEnd = strrev(implode('/', $acc));
2287  if ((string)$commonEnd !== '') {
2288  $retVal = substr($SFN, 0, -(strlen($commonEnd) + 1));
2289  }
2290  break;
2291  case 'TYPO3_HOST_ONLY':
2292  $httpHost = self::getIndpEnv('HTTP_HOST');
2293  $httpHostBracketPosition = strpos($httpHost, ']');
2294  $httpHostParts = explode(':', $httpHost);
2295  $retVal = $httpHostBracketPosition !== false ? substr($httpHost, 0, $httpHostBracketPosition + 1) : array_shift($httpHostParts);
2296  break;
2297  case 'TYPO3_PORT':
2298  $httpHost = self::getIndpEnv('HTTP_HOST');
2299  $httpHostOnly = self::getIndpEnv('TYPO3_HOST_ONLY');
2300  $retVal = strlen($httpHost) > strlen($httpHostOnly) ? substr($httpHost, strlen($httpHostOnly) + 1) : '';
2301  break;
2302  case 'TYPO3_REQUEST_HOST':
2303  $retVal = (self::getIndpEnv('TYPO3_SSL') ? 'https://' : 'http://') . self::getIndpEnv('HTTP_HOST');
2304  break;
2305  case 'TYPO3_REQUEST_URL':
2306  $retVal = self::getIndpEnv('TYPO3_REQUEST_HOST') . self::getIndpEnv('REQUEST_URI');
2307  break;
2308  case 'TYPO3_REQUEST_SCRIPT':
2309  $retVal = self::getIndpEnv('TYPO3_REQUEST_HOST') . self::getIndpEnv('SCRIPT_NAME');
2310  break;
2311  case 'TYPO3_REQUEST_DIR':
2312  $retVal = self::getIndpEnv('TYPO3_REQUEST_HOST') . self::dirname(self::getIndpEnv('SCRIPT_NAME')) . '/';
2313  break;
2314  case 'TYPO3_SITE_URL':
2317  ‪$url = self::getIndpEnv('TYPO3_REQUEST_DIR');
2318  $siteUrl = substr(‪$url, 0, -strlen($lPath));
2319  if (substr($siteUrl, -1) !== '/') {
2320  $siteUrl .= '/';
2321  }
2322  $retVal = $siteUrl;
2323  }
2324  break;
2325  case 'TYPO3_SITE_PATH':
2326  $retVal = substr(self::getIndpEnv('TYPO3_SITE_URL'), strlen(self::getIndpEnv('TYPO3_REQUEST_HOST')));
2327  break;
2328  case 'TYPO3_SITE_SCRIPT':
2329  $retVal = substr(self::getIndpEnv('TYPO3_REQUEST_URL'), strlen(self::getIndpEnv('TYPO3_SITE_URL')));
2330  break;
2331  case 'TYPO3_SSL':
2332  $proxySSL = trim(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxySSL'] ?? '');
2333  if ($proxySSL === '*') {
2334  $proxySSL = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyIP'];
2335  }
2336  if (self::cmpIP($_SERVER['REMOTE_ADDR'] ?? '', $proxySSL)) {
2337  $retVal = true;
2338  } else {
2339  $retVal = self::webserverUsesHttps();
2340  }
2341  break;
2342  case '_ARRAY':
2343  $out = [];
2344  // Here, list ALL possible keys to this function for debug display.
2345  $envTestVars = [
2346  'HTTP_HOST',
2347  'TYPO3_HOST_ONLY',
2348  'TYPO3_PORT',
2349  'PATH_INFO',
2350  'QUERY_STRING',
2351  'REQUEST_URI',
2352  'HTTP_REFERER',
2353  'TYPO3_REQUEST_HOST',
2354  'TYPO3_REQUEST_URL',
2355  'TYPO3_REQUEST_SCRIPT',
2356  'TYPO3_REQUEST_DIR',
2357  'TYPO3_SITE_URL',
2358  'TYPO3_SITE_SCRIPT',
2359  'TYPO3_SSL',
2360  'TYPO3_REV_PROXY',
2361  'SCRIPT_NAME',
2362  'TYPO3_DOCUMENT_ROOT',
2363  'SCRIPT_FILENAME',
2364  'REMOTE_ADDR',
2365  'REMOTE_HOST',
2366  'HTTP_USER_AGENT',
2367  'HTTP_ACCEPT_LANGUAGE',
2368  ];
2369  foreach ($envTestVars as $v) {
2370  $out[$v] = self::getIndpEnv($v);
2371  }
2372  reset($out);
2373  $retVal = $out;
2374  break;
2375  }
2376  self::$indpEnvCache[$getEnvName] = $retVal;
2377  return $retVal;
2378  }
2379 
2390  protected static function webserverUsesHttps()
2391  {
2392  if (!empty($_SERVER['SSL_SESSION_ID'])) {
2393  return true;
2394  }
2395 
2396  // https://secure.php.net/manual/en/reserved.variables.server.php
2397  // "Set to a non-empty value if the script was queried through the HTTPS protocol."
2398  return !empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off';
2399  }
2400 
2401  protected static function encodeFileSystemPathComponentForUrlPath(string $path): string
2402  {
2403  return implode('/', array_map(rawurlencode(...), explode('/', $path)));
2404  }
2405 
2406  /*************************
2407  *
2408  * TYPO3 SPECIFIC FUNCTIONS
2409  *
2410  *************************/
2420  public static function getFileAbsFileName($filename)
2421  {
2422  $fileName = (string)$filename;
2423  if ($fileName === '') {
2424  return '';
2425  }
2426  $checkForBackPath = fn (string $fileName): string => $fileName !== '' && static::validPathStr($fileName) ? $fileName : '';
2427 
2428  // Extension "EXT:" path resolving.
2429  if (‪PathUtility::isExtensionPath($fileName)) {
2430  try {
2432  } catch (‪PackageException) {
2433  $fileName = '';
2434  }
2435  return $checkForBackPath($fileName);
2436  }
2437 
2438  // Absolute path, but set to blank if not inside allowed directories.
2439  if (‪PathUtility::isAbsolutePath($fileName)) {
2440  if (str_starts_with($fileName, ‪Environment::getProjectPath()) ||
2441  str_starts_with($fileName, ‪Environment::getPublicPath())) {
2442  return $checkForBackPath($fileName);
2443  }
2444  return '';
2445  }
2446 
2447  // Relative path. Prepend with the public web folder.
2448  $fileName = ‪Environment::getPublicPath() . '/' . $fileName;
2449  return $checkForBackPath($fileName);
2450  }
2451 
2463  public static function validPathStr(string $theFile): bool
2464  {
2465  return !str_contains($theFile, '//') && !str_contains($theFile, '\\')
2466  && preg_match('#(?:^\\.\\.|/\\.\\./|[[:cntrl:]])#u', $theFile) === 0;
2467  }
2468 
2475  public static function isAllowedAbsPath($path)
2476  {
2477  if (substr($path, 0, 6) === 'vfs://') {
2478  return true;
2479  }
2480  $lockRootPath = ‪$GLOBALS['TYPO3_CONF_VARS']['BE']['lockRootPath'] ?? '';
2481  return ‪PathUtility::isAbsolutePath($path) && static::validPathStr($path)
2482  && (
2483  str_starts_with($path, ‪Environment::getProjectPath())
2484  || str_starts_with($path, ‪Environment::getPublicPath())
2485  || ($lockRootPath && str_starts_with($path, $lockRootPath))
2486  );
2487  }
2488 
2495  public static function copyDirectory($source, $destination)
2496  {
2497  if (!str_contains($source, ‪Environment::getProjectPath() . '/')) {
2498  $source = ‪Environment::getPublicPath() . '/' . $source;
2499  }
2500  if (!str_contains($destination, ‪Environment::getProjectPath() . '/')) {
2501  $destination = ‪Environment::getPublicPath() . '/' . $destination;
2502  }
2503  if (static::isAllowedAbsPath($source) && static::isAllowedAbsPath($destination)) {
2504  static::mkdir_deep($destination);
2505  $iterator = new \RecursiveIteratorIterator(
2506  new \RecursiveDirectoryIterator($source, \RecursiveDirectoryIterator::SKIP_DOTS),
2507  \RecursiveIteratorIterator::SELF_FIRST
2508  );
2510  foreach ($iterator as $item) {
2511  $target = $destination . '/' . static::fixWindowsFilePath((string)$iterator->getSubPathName());
2512  if ($item->isDir()) {
2513  static::mkdir($target);
2514  } else {
2515  static::upload_copy_move(static::fixWindowsFilePath($item->getPathname()), $target);
2516  }
2517  }
2518  }
2519  }
2520 
2531  public static function sanitizeLocalUrl(string ‪$url): string
2532  {
2533  $sanitizedUrl = '';
2534  if (!empty(‪$url)) {
2535  $decodedUrl = rawurldecode(‪$url);
2536  $parsedUrl = parse_url($decodedUrl);
2537  $testAbsoluteUrl = self::resolveBackPath($decodedUrl);
2538  $testRelativeUrl = self::resolveBackPath(self::dirname(self::getIndpEnv('SCRIPT_NAME')) . '/' . $decodedUrl);
2539  // Pass if URL is on the current host:
2540  if (self::isValidUrl($decodedUrl)) {
2541  if (self::isOnCurrentHost($decodedUrl) && str_starts_with($decodedUrl, self::getIndpEnv('TYPO3_SITE_URL'))) {
2542  $sanitizedUrl = ‪$url;
2543  }
2544  } elseif (‪PathUtility::isAbsolutePath($decodedUrl) && self::isAllowedAbsPath($decodedUrl)) {
2545  $sanitizedUrl = ‪$url;
2546  } elseif (str_starts_with($testAbsoluteUrl, self::getIndpEnv('TYPO3_SITE_PATH')) && $decodedUrl[0] === '/' &&
2547  substr($decodedUrl, 0, 2) !== '//'
2548  ) {
2549  $sanitizedUrl = ‪$url;
2550  } elseif (empty($parsedUrl['scheme']) && str_starts_with($testRelativeUrl, self::getIndpEnv('TYPO3_SITE_PATH'))
2551  && $decodedUrl[0] !== '/' && strpbrk($decodedUrl, '*:|"<>') === false && !str_contains($decodedUrl, '\\\\')
2552  ) {
2553  $sanitizedUrl = ‪$url;
2554  }
2555  }
2556  if (!empty(‪$url) && empty($sanitizedUrl)) {
2557  static::getLogger()->notice('The URL "{url}" is not considered to be local and was denied.', ['url' => ‪$url]);
2558  }
2559  return $sanitizedUrl;
2560  }
2561 
2570  public static function upload_copy_move($source, $destination)
2571  {
2572  if (is_array(‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][\‪TYPO3\CMS\Core\Utility\GeneralUtility::class]['moveUploadedFile'] ?? null)) {
2573  $params = ['source' => $source, 'destination' => $destination, 'method' => 'upload_copy_move'];
2574  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][\‪TYPO3\CMS\Core\Utility\GeneralUtility::class]['moveUploadedFile'] as $hookMethod) {
2575  $fakeThis = null;
2576  self::callUserFunction($hookMethod, $params, $fakeThis);
2577  }
2578  }
2579 
2580  $result = false;
2581  if (is_uploaded_file($source)) {
2582  // Return the value of move_uploaded_file, and if FALSE the temporary $source is still
2583  // around so the user can use unlink to delete it:
2584  $result = move_uploaded_file($source, $destination);
2585  } else {
2586  @copy($source, $destination);
2587  }
2588  // Change the permissions of the file
2589  ‪self::fixPermissions($destination);
2590  // If here the file is copied and the temporary $source is still around,
2591  // so when returning FALSE the user can try unlink to delete the $source
2592  return $result;
2593  }
2594 
2605  public static function upload_to_tempfile($uploadedFileName)
2606  {
2607  if (is_uploaded_file($uploadedFileName)) {
2608  $tempFile = self::tempnam('upload_temp_');
2609  if (is_array(‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][\‪TYPO3\CMS\Core\Utility\GeneralUtility::class]['moveUploadedFile'] ?? null)) {
2610  $params = ['source' => $uploadedFileName, 'destination' => $tempFile, 'method' => 'upload_to_tempfile'];
2611  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][\‪TYPO3\CMS\Core\Utility\GeneralUtility::class]['moveUploadedFile'] as $hookMethod) {
2612  $fakeThis = null;
2613  self::callUserFunction($hookMethod, $params, $fakeThis);
2614  }
2615  }
2616 
2617  move_uploaded_file($uploadedFileName, $tempFile);
2618  return @is_file($tempFile) ? $tempFile : '';
2619  }
2620 
2621  return '';
2622  }
2623 
2634  public static function unlink_tempfile($uploadedTempFileName)
2635  {
2636  if ($uploadedTempFileName) {
2637  $uploadedTempFileName = self::fixWindowsFilePath((string)$uploadedTempFileName);
2638  if (
2639  self::validPathStr($uploadedTempFileName)
2640  && (
2641  str_starts_with($uploadedTempFileName, ‪Environment::getPublicPath() . '/typo3temp/')
2642  || str_starts_with($uploadedTempFileName, ‪Environment::getVarPath() . '/')
2643  )
2644  && @is_file($uploadedTempFileName)
2645  ) {
2646  if (unlink($uploadedTempFileName)) {
2647  return true;
2648  }
2649  }
2650  }
2651 
2652  return null;
2653  }
2654 
2666  public static function tempnam($filePrefix, $fileSuffix = '')
2667  {
2668  $temporaryPath = ‪Environment::getVarPath() . '/transient/';
2669  if (!is_dir($temporaryPath)) {
2670  ‪self::mkdir_deep($temporaryPath);
2671  }
2672  if ($fileSuffix === '') {
2673  $path = (string)tempnam($temporaryPath, $filePrefix);
2674  $tempFileName = $temporaryPath . ‪PathUtility::basename($path);
2675  } else {
2676  do {
2677  $tempFileName = $temporaryPath . $filePrefix . random_int(1, PHP_INT_MAX) . $fileSuffix;
2678  } while (file_exists($tempFileName));
2679  touch($tempFileName);
2680  clearstatcache(false, $tempFileName);
2681  }
2682  return $tempFileName;
2683  }
2684 
2695  public static function callUserFunction($funcName, &$params, ?object $ref = null)
2696  {
2697  // Check if we're using a closure and invoke it directly.
2698  if (is_object($funcName) && is_a($funcName, \Closure::class)) {
2699  return call_user_func_array($funcName, [&$params, &$ref]);
2700  }
2701  $funcName = trim($funcName);
2702  $parts = explode('->', $funcName);
2703  // Call function or method
2704  if (count($parts) === 2) {
2705  // It's a class/method
2706  // Check if class/method exists:
2707  if (class_exists($parts[0])) {
2708  // Create object
2709  $classObj = self::makeInstance($parts[0]);
2710  $methodName = (string)$parts[1];
2711  $callable = [$classObj, $methodName];
2712  if (is_callable($callable)) {
2713  // Call method:
2714  $content = call_user_func_array($callable, [&$params, &$ref]);
2715  } else {
2716  throw new \InvalidArgumentException('No method name \'' . $parts[1] . '\' in class ' . $parts[0], 1294585865);
2717  }
2718  } else {
2719  throw new \InvalidArgumentException('No class named ' . $parts[0], 1294585866);
2720  }
2721  } elseif (function_exists($funcName) && is_callable($funcName)) {
2722  // It's a function
2723  $content = call_user_func_array($funcName, [&$params, &$ref]);
2724  } else {
2725  // Usually this will be annotated by static code analysis tools, but there's no native "not empty string" type
2726  throw new \InvalidArgumentException('No function named: ' . $funcName, 1294585867);
2727  }
2728  return $content;
2729  }
2730 
2734  public static function setContainer(ContainerInterface ‪$container): void
2735  {
2736  self::$container = ‪$container;
2737  }
2738 
2742  public static function getContainer(): ContainerInterface
2743  {
2744  if (self::$container === null) {
2745  throw new \LogicException('PSR-11 Container is not available', 1549404144);
2746  }
2747  return ‪self::$container;
2748  }
2749 
2764  public static function makeInstance(string $className, mixed ...$constructorArguments): object
2765  {
2766  // PHPStan will complain about this check. That's okay as we're checking a contract violation here.
2767  if ($className === '') {
2768  throw new \InvalidArgumentException('$className must be a non empty string.', 1288965219);
2769  }
2770  // Never instantiate with a beginning backslash, otherwise things like singletons won't work.
2771  if (str_starts_with($className, '\\')) {
2772  throw new \InvalidArgumentException(
2773  '$className "' . $className . '" must not start with a backslash.',
2774  1420281366
2775  );
2776  }
2777  if (isset(static::$finalClassNameCache[$className])) {
2778  $finalClassName = static::$finalClassNameCache[$className];
2779  } else {
2780  $finalClassName = self::getClassName($className);
2781  static::$finalClassNameCache[$className] = $finalClassName;
2782  }
2783  // Return singleton instance if it is already registered
2784  if (isset(self::$singletonInstances[$finalClassName])) {
2785  return self::$singletonInstances[$finalClassName];
2786  }
2787  // Return instance if it has been injected by addInstance()
2788  if (
2789  isset(self::$nonSingletonInstances[$finalClassName])
2790  && !empty(self::$nonSingletonInstances[$finalClassName])
2791  ) {
2792  return array_shift(self::$nonSingletonInstances[$finalClassName]);
2793  }
2794 
2795  // Read service and prototypes from the DI container, this is required to
2796  // support classes that require dependency injection.
2797  // We operate on the original class name on purpose, as class overrides
2798  // are resolved inside the container
2799  if (self::$container !== null && $constructorArguments === [] && self::$container->has($className)) {
2800  return self::$container->get($className);
2801  }
2802 
2803  // Create new instance and call constructor with parameters
2804  $instance = new $finalClassName(...$constructorArguments);
2805  // Register new singleton instance, but only if it is not a known PSR-11 container service
2806  if ($instance instanceof SingletonInterface && !(self::$container !== null && self::$container->has($className))) {
2807  self::$singletonInstances[$finalClassName] = $instance;
2808  }
2809  if ($instance instanceof LoggerAwareInterface) {
2810  $instance->setLogger(static::makeInstance(LogManager::class)->getLogger($className));
2811  }
2812  return $instance;
2813  }
2814 
2826  public static function makeInstanceForDi(string $className, ...$constructorArguments): object
2827  {
2828  $finalClassName = static::$finalClassNameCache[$className] ?? static::$finalClassNameCache[$className] = self::getClassName($className);
2829 
2830  // Return singleton instance if it is already registered (currently required for unit and functional tests)
2831  if (isset(self::$singletonInstances[$finalClassName])) {
2832  return self::$singletonInstances[$finalClassName];
2833  }
2834  // Create new instance and call constructor with parameters
2835  return new $finalClassName(...$constructorArguments);
2836  }
2837 
2847  public static function getClassName($className)
2848  {
2849  if (class_exists($className)) {
2850  while (static::classHasImplementation($className)) {
2851  $className = static::getImplementationForClass($className);
2852  }
2853  }
2855  }
2856 
2863  protected static function getImplementationForClass($className)
2864  {
2865  return ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][$className]['className'];
2866  }
2867 
2874  protected static function classHasImplementation($className)
2875  {
2876  return !empty(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][$className]['className']);
2877  }
2878 
2895  public static function setSingletonInstance($className, SingletonInterface $instance)
2896  {
2897  self::checkInstanceClassName($className, $instance);
2898  // Check for XCLASS registration (same is done in makeInstance() in order to store the singleton of the final class name)
2899  $finalClassName = self::getClassName($className);
2900  self::$singletonInstances[$finalClassName] = $instance;
2901  }
2902 
2917  public static function removeSingletonInstance($className, SingletonInterface $instance)
2918  {
2919  self::checkInstanceClassName($className, $instance);
2920  if (!isset(self::$singletonInstances[$className])) {
2921  throw new \InvalidArgumentException('No Instance registered for ' . $className . '.', 1394099179);
2922  }
2923  if ($instance !== self::$singletonInstances[$className]) {
2924  throw new \InvalidArgumentException('The instance you are trying to remove has not been registered before.', 1394099256);
2925  }
2926  unset(self::$singletonInstances[$className]);
2927  }
2928 
2942  public static function resetSingletonInstances(array $newSingletonInstances)
2943  {
2944  static::$singletonInstances = [];
2945  foreach ($newSingletonInstances as $className => $instance) {
2946  static::setSingletonInstance($className, $instance);
2947  }
2948  }
2949 
2962  public static function getSingletonInstances()
2963  {
2964  return static::$singletonInstances;
2965  }
2966 
2978  public static function getInstances()
2979  {
2980  return static::$nonSingletonInstances;
2981  }
2982 
2997  public static function addInstance($className, $instance)
2998  {
2999  self::checkInstanceClassName($className, $instance);
3000  if ($instance instanceof SingletonInterface) {
3001  throw new \InvalidArgumentException('$instance must not be an instance of TYPO3\\CMS\\Core\\SingletonInterface. For setting singletons, please use setSingletonInstance.', 1288969325);
3002  }
3003  if (!isset(self::$nonSingletonInstances[$className])) {
3004  self::$nonSingletonInstances[$className] = [];
3005  }
3006  self::$nonSingletonInstances[$className][] = $instance;
3007  }
3008 
3016  protected static function checkInstanceClassName($className, $instance)
3017  {
3018  if ($className === '') {
3019  throw new \InvalidArgumentException('$className must not be empty.', 1288967479);
3020  }
3021  if (!$instance instanceof $className) {
3022  throw new \InvalidArgumentException('$instance must be an instance of ' . $className . ', but actually is an instance of ' . get_class($instance) . '.', 1288967686);
3023  }
3024  }
3025 
3036  public static function purgeInstances()
3037  {
3038  self::$container = null;
3039  self::$singletonInstances = [];
3040  self::$nonSingletonInstances = [];
3041  }
3042 
3052  public static function flushInternalRuntimeCaches()
3053  {
3054  self::$finalClassNameCache = [];
3055  self::$indpEnvCache = [];
3056  }
3057 
3072  public static function makeInstanceService(string $serviceType, string $serviceSubType = '', array $excludeServiceKeys = []): array|object|false
3073  {
3074  $error = false;
3075  $requestInfo = [
3076  'requestedServiceType' => $serviceType,
3077  'requestedServiceSubType' => $serviceSubType,
3078  'requestedExcludeServiceKeys' => $excludeServiceKeys,
3079  ];
3080  while ($info = ‪ExtensionManagementUtility::findService($serviceType, $serviceSubType, $excludeServiceKeys)) {
3081  // provide information about requested service to service object
3082  $info = array_merge($info, $requestInfo);
3083 
3085  $className = $info['className'];
3087  $obj = self::makeInstance($className);
3088  if (is_object($obj)) {
3089  if (!is_callable([$obj, 'init'])) {
3090  self::getLogger()->error('Requested service {class} has no init() method.', [
3091  'class' => $info['className'],
3092  'service' => $info,
3093  ]);
3094  throw new \RuntimeException('Broken service: ' . $info['className'], 1568119209);
3095  }
3096  $obj->info = $info;
3097  // service available?
3098  if ($obj->init()) {
3099  return $obj;
3100  }
3101  $error = $obj->getLastErrorArray();
3102  unset($obj);
3103  }
3104 
3105  // deactivate the service
3106  ‪ExtensionManagementUtility::deactivateService($info['serviceType'], $info['serviceKey']);
3107  }
3108  return $error;
3109  }
3110 
3117  public static function quoteJSvalue($value)
3118  {
3119  $json = (string)json_encode(
3120  (string)$value,
3121  JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_TAG
3122  );
3123 
3124  return strtr(
3125  $json,
3126  [
3127  '"' => '\'',
3128  '\\\\' => '\\u005C',
3129  ' ' => '\\u0020',
3130  '!' => '\\u0021',
3131  '\\t' => '\\u0009',
3132  '\\n' => '\\u000A',
3133  '\\r' => '\\u000D',
3134  ]
3135  );
3136  }
3137 
3147  public static function jsonEncodeForHtmlAttribute($value, bool $useHtmlEntities = true): string
3148  {
3149  $json = (string)json_encode($value, JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_TAG);
3150  return $useHtmlEntities ? htmlspecialchars($json) : $json;
3151  }
3152 
3161  public static function jsonEncodeForJavaScript($value): string
3162  {
3163  $json = (string)json_encode($value, JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_TAG);
3164  return strtr(
3165  $json,
3166  [
3167  // comments below refer to JSON-encoded data
3168  '\\\\' => '\\\\u005C', // `"\\Vendor\\Package"` -> `"\\u005CVendor\\u005CPackage"`
3169  '\\t' => '\\u0009', // `"\t"` -> `"\u0009"`
3170  '\\n' => '\\u000A', // `"\n"` -> `"\u000A"`
3171  '\\r' => '\\u000D', // `"\r"` -> `"\u000D"`
3172  ]
3173  );
3174  }
3175 
3180  public static function sanitizeCssVariableValue(string $value): string
3181  {
3182  $value = str_replace(['{', '}', "\n", "\r"], '', $value);
3183  // keep quotes, e.g. for `background: url("/res/background.png")`
3184  return htmlspecialchars($value, ENT_SUBSTITUTE);
3185  }
3186 
3190  protected static function getLogger()
3191  {
3192  return static::makeInstance(LogManager::class)->getLogger(__CLASS__);
3193  }
3194 }
‪TYPO3\CMS\Core\Utility\GeneralUtility\underscoredToLowerCamelCase
‪static string underscoredToLowerCamelCase($string)
Definition: GeneralUtility.php:649
‪TYPO3\CMS\Core\Utility\GeneralUtility\xml2array
‪static mixed xml2array($string, $NSprefix='', $reportDocTag=false)
Definition: GeneralUtility.php:1248
‪TYPO3\CMS\Core\Utility\PathUtility\stripPathSitePrefix
‪static stripPathSitePrefix(string $path)
Definition: PathUtility.php:428
‪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:93
‪TYPO3\CMS\Core\Utility\PathUtility\isAbsolutePath
‪static isAbsolutePath(string $path)
Definition: PathUtility.php:286
‪TYPO3
‪TYPO3\CMS\Core\Core\Environment\getPublicPath
‪static getPublicPath()
Definition: Environment.php:187
‪TYPO3\CMS\Core\Utility\GeneralUtility\$localeInfo
‪$localeInfo
Definition: GeneralUtility.php:568
‪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:560
‪$parser
‪$parser
Definition: annotationChecker.php:108
‪TYPO3\CMS\Core\Core\Environment\getCurrentScript
‪static getCurrentScript()
Definition: Environment.php:220
‪TYPO3\CMS\Core\Utility\GeneralUtility\cmpIP
‪static bool cmpIP(string $baseIP, string $list)
Definition: GeneralUtility.php:114
‪TYPO3\CMS\Core\Utility\GeneralUtility\$multiplier
‪$multiplier
Definition: GeneralUtility.php:572
‪TYPO3\CMS\Core\Utility\ExtensionManagementUtility\deactivateService
‪static deactivateService(string $serviceType, string $serviceKey)
Definition: ExtensionManagementUtility.php:829
‪TYPO3\CMS\Core\Utility\GeneralUtility\camelCaseToLowerCaseUnderscored
‪static string camelCaseToLowerCaseUnderscored($string)
Definition: GeneralUtility.php:661
‪$dir
‪$dir
Definition: validateRstFiles.php:257
‪TYPO3\CMS\Core\Utility\GeneralUtility\cmpFQDN
‪static bool cmpFQDN(string $baseHost, string $list)
Definition: GeneralUtility.php:342
‪TYPO3\CMS\Core\Utility\GeneralUtility\createDirectoryPath
‪static string createDirectoryPath($fullDirectoryPath)
Definition: GeneralUtility.php:1664
‪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\PathUtility\basename
‪static basename(string $path)
Definition: PathUtility.php:219
‪TYPO3\CMS\Core\Utility\GeneralUtility\$oldLocale
‪$oldLocale
Definition: GeneralUtility.php:566
‪TYPO3\CMS\Core\Utility\GeneralUtility\validIPv4
‪static bool validIPv4(string $ip)
Definition: GeneralUtility.php:317
‪TYPO3\CMS\Core\Utility\GeneralUtility\$nonSingletonInstances
‪static array $nonSingletonInstances
Definition: GeneralUtility.php:66
‪TYPO3\CMS\Core\Utility\GeneralUtility\get_dirs
‪static string[] string null get_dirs($path)
Definition: GeneralUtility.php:1748
‪TYPO3\CMS\Core\Utility\GeneralUtility\$indpEnvCache
‪static array $indpEnvCache
Definition: GeneralUtility.php:79
‪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:573
‪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:1230
‪TYPO3\CMS\Core\Utility\GeneralUtility\fixPermissions
‪static mixed fixPermissions($path, $recursive=false)
Definition: GeneralUtility.php:1479
‪TYPO3\CMS\Core\Utility\GeneralUtility\getUrl
‪static string false getUrl(string $url)
Definition: GeneralUtility.php:1427
‪TYPO3\CMS\Core\Utility\GeneralUtility\validIPv6
‪static bool validIPv6(string $ip)
Definition: GeneralUtility.php:330
‪TYPO3\CMS\Core\Utility\GeneralUtility\mkdir_deep
‪static mkdir_deep($directory)
Definition: GeneralUtility.php:1638
‪TYPO3\CMS\Core\Utility\GeneralUtility\expandList
‪static string expandList($list)
Definition: GeneralUtility.php:435
‪TYPO3\CMS\Core\Utility\ExtensionManagementUtility\findService
‪static array false findService(string $serviceType, string $serviceSubType='', array $excludeServiceKeys=[])
Definition: ExtensionManagementUtility.php:729
‪$validator
‪if(isset($args['d'])) $validator
Definition: validateRstFiles.php:262
‪TYPO3\CMS\Core\Utility\GeneralUtility\cmpIPv4
‪static bool cmpIPv4(string $baseIP, string $list)
Definition: GeneralUtility.php:136
‪TYPO3\CMS\Core\Utility\GeneralUtility\$singletonInstances
‪static array $singletonInstances
Definition: GeneralUtility.php:59
‪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:475
‪TYPO3\CMS\Core\Cache\CacheManager
Definition: CacheManager.php:36
‪TYPO3\CMS\Core\Utility\GeneralUtility\xml2arrayProcess
‪static mixed xml2arrayProcess($string, $NSprefix='', $reportDocTag=false)
Definition: GeneralUtility.php:1271
‪TYPO3\CMS\Core\Core\ClassLoadingInformation\getClassNameForAlias
‪static class string getClassNameForAlias($alias)
Definition: ClassLoadingInformation.php:179
‪TYPO3\CMS\Core\Utility\GeneralUtility\cmpIPv6
‪static bool cmpIPv6(string $baseIP, string $list)
Definition: GeneralUtility.php:185
‪TYPO3\CMS\Core\Utility\GeneralUtility\$sizeInBytes
‪$sizeInBytes
Definition: GeneralUtility.php:571
‪TYPO3\CMS\Core\Http\RequestFactory
Definition: RequestFactory.php:30
‪TYPO3\CMS\Core\Utility\GeneralUtility\$currentLocale
‪$currentLocale
Definition: GeneralUtility.php:565
‪TYPO3\CMS\Core\Utility\GeneralUtility\$finalClassNameCache
‪static array $finalClassNameCache
Definition: GeneralUtility.php:74
‪TYPO3\CMS\Core\Utility\GeneralUtility\$output
‪$output
Definition: GeneralUtility.php:1138
‪TYPO3\CMS\Webhooks\Message\$url
‪identifier readonly UriInterface $url
Definition: LoginErrorOccurredMessage.php:36
‪TYPO3\CMS\Core\Utility\GeneralUtility\normalizeIPv6
‪static string normalizeIPv6(string $address)
Definition: GeneralUtility.php:240
‪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\rmdir
‪static bool rmdir($path, $removeNonEmpty=false)
Definition: GeneralUtility.php:1691
‪TYPO3\CMS\Core\Utility\GeneralUtility\isValidUrl
‪static bool isValidUrl(string $url)
Definition: GeneralUtility.php:691
‪TYPO3\CMS\Core\Utility\GeneralUtility\inList
‪static bool inList($list, $item)
Definition: GeneralUtility.php:423
‪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:1377
‪TYPO3\CMS\Core\Utility\GeneralUtility\validIP
‪static bool validIP(string $ip)
Definition: GeneralUtility.php:304
‪TYPO3\CMS\Core\Package\Exception
Definition: InvalidPackageKeyException.php:16
‪TYPO3\CMS\Core\Http\fromRequest
‪@ fromRequest
Definition: ApplicationType.php:67
‪TYPO3\CMS\Core\Utility\GeneralUtility\md5int
‪static int md5int($str)
Definition: GeneralUtility.php:463
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:51
‪TYPO3\CMS\Core\Utility\GeneralUtility\mkdir
‪static bool mkdir($newFolder)
Definition: GeneralUtility.php:1621
‪TYPO3\CMS\Core\Utility\GeneralUtility\writeFile
‪static bool writeFile($file, $content, $changePermissions=false)
Definition: GeneralUtility.php:1452
‪TYPO3\CMS\Core\Utility\GeneralUtility\isOnCurrentHost
‪static bool isOnCurrentHost(string $url)
Definition: GeneralUtility.php:410
‪TYPO3\CMS\Core\Utility\GeneralUtility\underscoredToUpperCamelCase
‪static string underscoredToUpperCamelCase($string)
Definition: GeneralUtility.php:637
‪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:1544
‪TYPO3\CMS\Core\Core\Environment\isWindows
‪static isWindows()
Definition: Environment.php:276