‪TYPO3CMS  9.5
GeneralUtility.php
Go to the documentation of this file.
1 <?php
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
17 use GuzzleHttp\Exception\RequestException;
18 use Psr\Http\Message\ServerRequestInterface;
19 use Psr\Log\LoggerAwareInterface;
20 use Psr\Log\LoggerInterface;
30 use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
31 
45 {
46  // Severity constants used by \TYPO3\CMS\Core\Utility\GeneralUtility::devLog()
47  // @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0.
53 
56 
63  protected static ‪$allowHostHeaderValue = false;
64 
71  protected static $singletonInstances = [];
72 
78  protected static $nonSingletonInstances = [];
79 
85  protected static $finalClassNameCache = [];
86 
92  protected static $applicationContext;
93 
99  protected static $idnaStringCache = [];
100 
106  protected static $supportedCgiServerApis = [
107  'fpm-fcgi',
108  'cgi',
109  'isapi',
110  'cgi-fcgi',
111  'srv', // HHVM with fastcgi
112  ];
113 
117  protected static $indpEnvCache = [];
118 
119  /*************************
120  *
121  * GET/POST Variables
122  *
123  * Background:
124  * Input GET/POST variables in PHP may have their quotes escaped with "\" or not depending on configuration.
125  * TYPO3 has always converted quotes to BE escaped if the configuration told that they would not be so.
126  * But the clean solution is that quotes are never escaped and that is what the functions below offers.
127  * Eventually TYPO3 should provide this in the global space as well.
128  * In the transitional phase (or forever..?) we need to encourage EVERY to read and write GET/POST vars through the API functions below.
129  * This functionality was previously needed to normalize between magic quotes logic, which was removed from PHP 5.4,
130  * so these methods are still in use, but not tackle the slash problem anymore.
131  *
132  *************************/
140  public static function _GP($var)
141  {
142  if (empty($var)) {
143  return;
144  }
145  if (isset($_POST[$var])) {
146  $value = $_POST[$var];
147  } elseif (isset($_GET[$var])) {
148  $value = $_GET[$var];
149  } else {
150  $value = null;
151  }
152  // This is there for backwards-compatibility, in order to avoid NULL
153  if (isset($value) && !is_array($value)) {
154  $value = (string)$value;
155  }
156  return $value;
157  }
158 
165  public static function _GPmerged($parameter)
166  {
167  $postParameter = isset($_POST[$parameter]) && is_array($_POST[$parameter]) ? $_POST[$parameter] : [];
168  $getParameter = isset($_GET[$parameter]) && is_array($_GET[$parameter]) ? $_GET[$parameter] : [];
169  $mergedParameters = $getParameter;
170  ‪ArrayUtility::mergeRecursiveWithOverrule($mergedParameters, $postParameter);
171  return $mergedParameters;
172  }
173 
182  public static function _GET($var = null)
183  {
184  $value = $var === null
185  ? $_GET
186  : (empty($var) ? null : ($_GET[$var] ?? null));
187  // This is there for backwards-compatibility, in order to avoid NULL
188  if (isset($value) && !is_array($value)) {
189  $value = (string)$value;
190  }
191  return $value;
192  }
193 
201  public static function _POST($var = null)
202  {
203  $value = $var === null ? $_POST : (empty($var) || !isset($_POST[$var]) ? null : $_POST[$var]);
204  // This is there for backwards-compatibility, in order to avoid NULL
205  if (isset($value) && !is_array($value)) {
206  $value = (string)$value;
207  }
208  return $value;
209  }
210 
218  public static function _GETset($inputGet, $key = '')
219  {
220  trigger_error('GeneralUtility::_GETset() will be removed in TYPO3 v10.0. Use a PSR-15 middleware to set query parameters on the request object or set $_GET directly.', E_USER_DEPRECATED);
221  if ($key != '') {
222  if (strpos($key, '|') !== false) {
223  $pieces = explode('|', $key);
224  $newGet = [];
225  $pointer = &$newGet;
226  foreach ($pieces as $piece) {
227  $pointer = &$pointer[$piece];
228  }
229  $pointer = $inputGet;
230  $mergedGet = $_GET;
231  ‪ArrayUtility::mergeRecursiveWithOverrule($mergedGet, $newGet);
232  $_GET = $mergedGet;
233  ‪$GLOBALS['HTTP_GET_VARS'] = $mergedGet;
234  } else {
235  $_GET[$key] = $inputGet;
236  ‪$GLOBALS['HTTP_GET_VARS'][$key] = $inputGet;
237  }
238  } elseif (is_array($inputGet)) {
239  $_GET = $inputGet;
240  ‪$GLOBALS['HTTP_GET_VARS'] = $inputGet;
241  if (isset(‪$GLOBALS['TYPO3_REQUEST']) && ‪$GLOBALS['TYPO3_REQUEST'] instanceof ServerRequestInterface) {
242  ‪$GLOBALS['TYPO3_REQUEST'] = ‪$GLOBALS['TYPO3_REQUEST']->withQueryParams($inputGet);
243  }
244  }
245  }
246 
247  /*************************
248  *
249  * STRING FUNCTIONS
250  *
251  *************************/
260  public static function fixed_lgd_cs($string, $chars, $appendString = '...')
261  {
262  if ((int)$chars === 0 || mb_strlen($string, 'utf-8') <= abs($chars)) {
263  return $string;
264  }
265  if ($chars > 0) {
266  $string = mb_substr($string, 0, $chars, 'utf-8') . $appendString;
267  } else {
268  $string = $appendString . mb_substr($string, $chars, mb_strlen($string, 'utf-8'), 'utf-8');
269  }
270  return $string;
271  }
272 
281  public static function cmpIP($baseIP, $list)
282  {
283  $list = trim($list);
284  if ($list === '') {
285  return false;
286  }
287  if ($list === '*') {
288  return true;
289  }
290  if (strpos($baseIP, ':') !== false && self::validIPv6($baseIP)) {
291  return self::cmpIPv6($baseIP, $list);
292  }
293  return self::cmpIPv4($baseIP, $list);
294  }
295 
303  public static function cmpIPv4($baseIP, $list)
304  {
305  $IPpartsReq = explode('.', $baseIP);
306  if (count($IPpartsReq) === 4) {
307  $values = self::trimExplode(',', $list, true);
308  foreach ($values as $test) {
309  $testList = explode('/', $test);
310  if (count($testList) === 2) {
311  list($test, $mask) = $testList;
312  } else {
313  $mask = false;
314  }
315  if ((int)$mask) {
316  // "192.168.3.0/24"
317  $lnet = ip2long($test);
318  $lip = ip2long($baseIP);
319  $binnet = str_pad(decbin($lnet), 32, '0', STR_PAD_LEFT);
320  $firstpart = substr($binnet, 0, $mask);
321  $binip = str_pad(decbin($lip), 32, '0', STR_PAD_LEFT);
322  $firstip = substr($binip, 0, $mask);
323  $yes = $firstpart === $firstip;
324  } else {
325  // "192.168.*.*"
326  $IPparts = explode('.', $test);
327  $yes = 1;
328  foreach ($IPparts as $index => $val) {
329  $val = trim($val);
330  if ($val !== '*' && $IPpartsReq[$index] !== $val) {
331  $yes = 0;
332  }
333  }
334  }
335  if ($yes) {
336  return true;
337  }
338  }
339  }
340  return false;
341  }
342 
350  public static function cmpIPv6($baseIP, $list)
351  {
352  // Policy default: Deny connection
353  $success = false;
354  $baseIP = self::normalizeIPv6($baseIP);
355  $values = self::trimExplode(',', $list, true);
356  foreach ($values as $test) {
357  $testList = explode('/', $test);
358  if (count($testList) === 2) {
359  list($test, $mask) = $testList;
360  } else {
361  $mask = false;
362  }
363  if (self::validIPv6($test)) {
364  $test = self::normalizeIPv6($test);
365  $maskInt = (int)$mask ?: 128;
366  // Special case; /0 is an allowed mask - equals a wildcard
367  if ($mask === '0') {
368  $success = true;
369  } elseif ($maskInt == 128) {
370  $success = $test === $baseIP;
371  } else {
372  $testBin = self::IPv6Hex2Bin($test);
373  $baseIPBin = self::IPv6Hex2Bin($baseIP);
374  $success = true;
375  // Modulo is 0 if this is a 8-bit-boundary
376  $maskIntModulo = $maskInt % 8;
377  $numFullCharactersUntilBoundary = (int)($maskInt / 8);
378  if (strpos($testBin, substr($baseIPBin, 0, $numFullCharactersUntilBoundary)) !== 0) {
379  $success = false;
380  } elseif ($maskIntModulo > 0) {
381  // If not an 8-bit-boundary, check bits of last character
382  $testLastBits = str_pad(decbin(ord(substr($testBin, $numFullCharactersUntilBoundary, 1))), 8, '0', STR_PAD_LEFT);
383  $baseIPLastBits = str_pad(decbin(ord(substr($baseIPBin, $numFullCharactersUntilBoundary, 1))), 8, '0', STR_PAD_LEFT);
384  if (strncmp($testLastBits, $baseIPLastBits, $maskIntModulo) != 0) {
385  $success = false;
386  }
387  }
388  }
389  }
390  if ($success) {
391  return true;
392  }
393  }
394  return false;
395  }
396 
404  public static function IPv6Hex2Bin($hex)
405  {
406  return inet_pton($hex);
407  }
408 
416  public static function IPv6Bin2Hex($bin)
417  {
418  return inet_ntop($bin);
419  }
420 
428  public static function normalizeIPv6($address)
429  {
430  $normalizedAddress = '';
431  $stageOneAddress = '';
432  // According to RFC lowercase-representation is recommended
433  $address = strtolower($address);
434  // Normalized representation has 39 characters (0000:0000:0000:0000:0000:0000:0000:0000)
435  if (strlen($address) === 39) {
436  // Already in full expanded form
437  return $address;
438  }
439  // Count 2 if if address has hidden zero blocks
440  $chunks = explode('::', $address);
441  if (count($chunks) === 2) {
442  $chunksLeft = explode(':', $chunks[0]);
443  $chunksRight = explode(':', $chunks[1]);
444  $left = count($chunksLeft);
445  $right = count($chunksRight);
446  // Special case: leading zero-only blocks count to 1, should be 0
447  if ($left === 1 && strlen($chunksLeft[0]) === 0) {
448  $left = 0;
449  }
450  $hiddenBlocks = 8 - ($left + $right);
451  $hiddenPart = '';
452  $h = 0;
453  while ($h < $hiddenBlocks) {
454  $hiddenPart .= '0000:';
455  $h++;
456  }
457  if ($left === 0) {
458  $stageOneAddress = $hiddenPart . $chunks[1];
459  } else {
460  $stageOneAddress = $chunks[0] . ':' . $hiddenPart . $chunks[1];
461  }
462  } else {
463  $stageOneAddress = $address;
464  }
465  // Normalize the blocks:
466  $blocks = explode(':', $stageOneAddress);
467  $divCounter = 0;
468  foreach ($blocks as $block) {
469  $tmpBlock = '';
470  $i = 0;
471  $hiddenZeros = 4 - strlen($block);
472  while ($i < $hiddenZeros) {
473  $tmpBlock .= '0';
474  $i++;
475  }
476  $normalizedAddress .= $tmpBlock . $block;
477  if ($divCounter < 7) {
478  $normalizedAddress .= ':';
479  $divCounter++;
480  }
481  }
482  return $normalizedAddress;
483  }
484 
492  public static function compressIPv6($address)
493  {
494  return inet_ntop(inet_pton($address));
495  }
496 
505  public static function validIP($ip)
506  {
507  return filter_var($ip, FILTER_VALIDATE_IP) !== false;
508  }
509 
518  public static function validIPv4($ip)
519  {
520  return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false;
521  }
522 
531  public static function validIPv6($ip)
532  {
533  return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false;
534  }
535 
543  public static function cmpFQDN($baseHost, $list)
544  {
545  $baseHost = trim($baseHost);
546  if (empty($baseHost)) {
547  return false;
548  }
549  if (self::validIPv4($baseHost) || self::validIPv6($baseHost)) {
550  // Resolve hostname
551  // Note: this is reverse-lookup and can be randomly set as soon as somebody is able to set
552  // the reverse-DNS for his IP (security when for example used with REMOTE_ADDR)
553  $baseHostName = gethostbyaddr($baseHost);
554  if ($baseHostName === $baseHost) {
555  // Unable to resolve hostname
556  return false;
557  }
558  } else {
559  $baseHostName = $baseHost;
560  }
561  $baseHostNameParts = explode('.', $baseHostName);
562  $values = self::trimExplode(',', $list, true);
563  foreach ($values as $test) {
564  $hostNameParts = explode('.', $test);
565  // To match hostNameParts can only be shorter (in case of wildcards) or equal
566  $hostNamePartsCount = count($hostNameParts);
567  $baseHostNamePartsCount = count($baseHostNameParts);
568  if ($hostNamePartsCount > $baseHostNamePartsCount) {
569  continue;
570  }
571  $yes = true;
572  foreach ($hostNameParts as $index => $val) {
573  $val = trim($val);
574  if ($val === '*') {
575  // Wildcard valid for one or more hostname-parts
576  $wildcardStart = $index + 1;
577  // Wildcard as last/only part always matches, otherwise perform recursive checks
578  if ($wildcardStart < $hostNamePartsCount) {
579  $wildcardMatched = false;
580  $tempHostName = implode('.', array_slice($hostNameParts, $index + 1));
581  while ($wildcardStart < $baseHostNamePartsCount && !$wildcardMatched) {
582  $tempBaseHostName = implode('.', array_slice($baseHostNameParts, $wildcardStart));
583  $wildcardMatched = self::cmpFQDN($tempBaseHostName, $tempHostName);
584  $wildcardStart++;
585  }
586  if ($wildcardMatched) {
587  // Match found by recursive compare
588  return true;
589  }
590  $yes = false;
591  }
592  } elseif ($baseHostNameParts[$index] !== $val) {
593  // In case of no match
594  $yes = false;
595  }
596  }
597  if ($yes) {
598  return true;
599  }
600  }
601  return false;
602  }
603 
611  public static function isOnCurrentHost($url)
612  {
613  return stripos($url . '/', self::getIndpEnv('TYPO3_REQUEST_HOST') . '/') === 0;
614  }
615 
624  public static function inList($list, $item)
625  {
626  return strpos(',' . $list . ',', ',' . $item . ',') !== false;
627  }
628 
639  public static function rmFromList($element, $list)
640  {
641  $items = explode(',', $list);
642  foreach ($items as $k => $v) {
643  if ($v == $element) {
644  unset($items[$k]);
645  }
646  }
647  return implode(',', $items);
648  }
649 
657  public static function expandList($list)
658  {
659  $items = explode(',', $list);
660  $list = [];
661  foreach ($items as $item) {
662  $range = explode('-', $item);
663  if (isset($range[1])) {
664  $runAwayBrake = 1000;
665  for ($n = $range[0]; $n <= $range[1]; $n++) {
666  $list[] = $n;
667  $runAwayBrake--;
668  if ($runAwayBrake <= 0) {
669  break;
670  }
671  }
672  } else {
673  $list[] = $item;
674  }
675  }
676  return implode(',', $list);
677  }
678 
685  public static function md5int($str)
686  {
687  return hexdec(substr(md5($str), 0, 7));
688  }
689 
697  public static function shortMD5($input, $len = 10)
698  {
699  return substr(md5($input), 0, $len);
700  }
701 
709  public static function hmac($input, $additionalSecret = '')
710  {
711  $hashAlgorithm = 'sha1';
712  $hashBlocksize = 64;
713  $secret = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'] . $additionalSecret;
714  if (extension_loaded('hash') && function_exists('hash_hmac') && function_exists('hash_algos') && in_array($hashAlgorithm, hash_algos())) {
715  $hmac = hash_hmac($hashAlgorithm, $input, $secret);
716  } else {
717  // Outer padding
718  $opad = str_repeat(chr(92), $hashBlocksize);
719  // Inner padding
720  $ipad = str_repeat(chr(54), $hashBlocksize);
721  if (strlen($secret) > $hashBlocksize) {
722  // Keys longer than block size are shorten
723  $key = str_pad(pack('H*', call_user_func($hashAlgorithm, $secret)), $hashBlocksize, "\0");
724  } else {
725  // Keys shorter than block size are zero-padded
726  $key = str_pad($secret, $hashBlocksize, "\0");
727  }
728  $hmac = call_user_func($hashAlgorithm, ($key ^ $opad) . pack('H*', call_user_func(
729  $hashAlgorithm,
730  ($key ^ $ipad) . $input
731  )));
732  }
733  return $hmac;
734  }
735 
744  public static function uniqueList($in_list, $secondParameter = null)
745  {
746  if (is_array($in_list)) {
747  throw new \InvalidArgumentException('TYPO3 Fatal Error: TYPO3\\CMS\\Core\\Utility\\GeneralUtility::uniqueList() does NOT support array arguments anymore! Only string comma lists!', 1270853885);
748  }
749  if (isset($secondParameter)) {
750  throw new \InvalidArgumentException('TYPO3 Fatal Error: TYPO3\\CMS\\Core\\Utility\\GeneralUtility::uniqueList() does NOT support more than a single argument value anymore. You have specified more than one!', 1270853886);
751  }
752  return implode(',', array_unique(self::trimExplode(',', $in_list, true)));
753  }
754 
761  public static function split_fileref($fileNameWithPath)
762  {
763  $reg = [];
764  if (preg_match('/(.*\\/)(.*)$/', $fileNameWithPath, $reg)) {
765  $info['path'] = $reg[1];
766  $info['file'] = $reg[2];
767  } else {
768  $info['path'] = '';
769  $info['file'] = $fileNameWithPath;
770  }
771  $reg = '';
772  // If open_basedir is set and the fileName was supplied without a path the is_dir check fails
773  if (!is_dir($fileNameWithPath) && preg_match('/(.*)\\.([^\\.]*$)/', $info['file'], $reg)) {
774  $info['filebody'] = $reg[1];
775  $info['fileext'] = strtolower($reg[2]);
776  $info['realFileext'] = $reg[2];
777  } else {
778  $info['filebody'] = $info['file'];
779  $info['fileext'] = '';
780  }
781  reset($info);
782  return $info;
783  }
784 
800  public static function dirname($path)
801  {
802  $p = self::revExplode('/', $path, 2);
803  return count($p) === 2 ? $p[0] : '';
804  }
805 
813  public static function isFirstPartOfStr($str, $partStr)
814  {
815  $str = is_array($str) ? '' : (string)$str;
816  $partStr = is_array($partStr) ? '' : (string)$partStr;
817  return $partStr !== '' && strpos($str, $partStr, 0) === 0;
818  }
819 
828  public static function formatSize(‪$sizeInBytes, $labels = '', $base = 0)
829  {
830  $defaultFormats = [
831  'iec' => ['base' => 1024, 'labels' => [' ', ' Ki', ' Mi', ' Gi', ' Ti', ' Pi', ' Ei', ' Zi', ' Yi']],
832  'si' => ['base' => 1000, 'labels' => [' ', ' k', ' M', ' G', ' T', ' P', ' E', ' Z', ' Y']],
833  ];
834  // Set labels and base:
835  if (empty($labels)) {
836  $labels = 'iec';
837  }
838  if (isset($defaultFormats[$labels])) {
839  $base = $defaultFormats[$labels]['base'];
840  ‪$labelArr = $defaultFormats[$labels]['labels'];
841  } else {
842  $base = (int)$base;
843  if ($base !== 1000 && $base !== 1024) {
844  $base = 1024;
845  }
846  ‪$labelArr = explode('|', str_replace('"', '', $labels));
847  }
848  // @todo find out which locale is used for current BE user to cover the BE case as well
849  ‪$oldLocale = setlocale(LC_NUMERIC, 0);
850  ‪$newLocale = ‪$GLOBALS['TSFE']->config['config']['locale_all'] ?? '';
852  setlocale(LC_NUMERIC, ‪$newLocale);
853  }
854  ‪$localeInfo = localeconv();
855  if (‪$newLocale) {
856  setlocale(LC_NUMERIC, ‪$oldLocale);
857  }
859  ‪$multiplier = floor((‪$sizeInBytes ? log(‪$sizeInBytes) : 0) / log($base));
861  if (‪$sizeInUnits > ($base * .9)) {
862  ‪$multiplier++;
863  }
864  ‪$multiplier = min(‪$multiplier, count(‪$labelArr) - 1);
866  return number_format(‪$sizeInUnits, ((‪$multiplier > 0) && (‪$sizeInUnits < 20)) ? 2 : 0, ‪$localeInfo['decimal_point'], '') . ‪$labelArr[‪$multiplier];
867  }
868 
877  public static function splitCalc($string, $operators)
878  {
879  $res = [];
880  $sign = '+';
881  while ($string) {
882  $valueLen = strcspn($string, $operators);
883  $value = substr($string, 0, $valueLen);
884  $res[] = [$sign, trim($value)];
885  $sign = substr($string, $valueLen, 1);
886  $string = substr($string, $valueLen + 1);
887  }
888  reset($res);
889  return $res;
890  }
891 
910  public static function validEmail($email)
911  {
912  // Early return in case input is not a string
913  if (!is_string($email)) {
914  return false;
915  }
916  $atPosition = strrpos($email, '@');
917  if (!$atPosition || $atPosition + 1 === strlen($email)) {
918  // Return if no @ found or it is placed at the very beginning or end of the email
919  return false;
920  }
921  $domain = substr($email, $atPosition + 1);
922  $user = substr($email, 0, $atPosition);
923  if (!preg_match('/^[a-z0-9.\\-]*$/i', $domain)) {
924  try {
925  $domain = self::idnaEncode($domain);
926  } catch (\InvalidArgumentException $exception) {
927  return false;
928  }
929  }
930  return filter_var($user . '@' . $domain, FILTER_VALIDATE_EMAIL) !== false;
931  }
932 
939  public static function idnaEncode($value)
940  {
941  if (isset(self::$idnaStringCache[$value])) {
942  return self::$idnaStringCache[$value];
943  }
944  // Early return in case input is not a string or empty
945  if (!is_string($value) || empty($value)) {
946  return (string)$value;
947  }
948 
949  // Split on the last "@" since addresses like "foo@bar"@example.org are valid where the only focus
950  // is an email address
951  $atPosition = strrpos($value, '@');
952  if ($atPosition !== false) {
953  $domain = substr($value, $atPosition + 1);
954  $local = substr($value, 0, $atPosition);
955  $domain = (string)‪HttpUtility::idn_to_ascii($domain);
956  // Return if no @ found or it is placed at the very beginning or end of the email
957  self::$idnaStringCache[$value] = $local . '@' . $domain;
958  return self::$idnaStringCache[$value];
959  }
960 
961  self::$idnaStringCache[$value] = (string)‪HttpUtility::idn_to_ascii($value);
962  return self::$idnaStringCache[$value];
963  }
964 
972  public static function underscoredToUpperCamelCase($string)
973  {
974  return str_replace(' ', '', ucwords(str_replace('_', ' ', strtolower($string))));
975  }
976 
984  public static function underscoredToLowerCamelCase($string)
985  {
986  return lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', strtolower($string)))));
987  }
988 
996  public static function camelCaseToLowerCaseUnderscored($string)
997  {
998  $value = preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $string);
999  return mb_strtolower($value, 'utf-8');
1000  }
1001 
1026  public static function isValidUrl($url)
1027  {
1028  $parsedUrl = parse_url($url);
1029  if (!$parsedUrl || !isset($parsedUrl['scheme'])) {
1030  return false;
1031  }
1032  // HttpUtility::buildUrl() will always build urls with <scheme>://
1033  // our original $url might only contain <scheme>: (e.g. mail:)
1034  // so we convert that to the double-slashed version to ensure
1035  // our check against the $recomposedUrl is proper
1036  if (!self::isFirstPartOfStr($url, $parsedUrl['scheme'] . '://')) {
1037  $url = str_replace($parsedUrl['scheme'] . ':', $parsedUrl['scheme'] . '://', $url);
1038  }
1039  $recomposedUrl = ‪HttpUtility::buildUrl($parsedUrl);
1040  if ($recomposedUrl !== $url) {
1041  // The parse_url() had to modify characters, so the URL is invalid
1042  return false;
1043  }
1044  if (isset($parsedUrl['host']) && !preg_match('/^[a-z0-9.\\-]*$/i', $parsedUrl['host'])) {
1045  try {
1046  $parsedUrl['host'] = self::idnaEncode($parsedUrl['host']);
1047  } catch (\InvalidArgumentException $exception) {
1048  return false;
1049  }
1050  }
1051  return filter_var(‪HttpUtility::buildUrl($parsedUrl), FILTER_VALIDATE_URL) !== false;
1052  }
1053 
1054  /*************************
1055  *
1056  * ARRAY FUNCTIONS
1057  *
1058  *************************/
1059 
1070  public static function intExplode($delimiter, $string, $removeEmptyValues = false, $limit = 0)
1071  {
1072  $result = explode($delimiter, $string);
1073  foreach ($result as $key => &$value) {
1074  if ($removeEmptyValues && ($value === '' || trim($value) === '')) {
1075  unset($result[$key]);
1076  } else {
1077  $value = (int)$value;
1078  }
1079  }
1080  unset($value);
1081  if ($limit !== 0) {
1082  if ($limit < 0) {
1083  $result = array_slice($result, 0, $limit);
1084  } elseif (count($result) > $limit) {
1085  $lastElements = array_slice($result, $limit - 1);
1086  $result = array_slice($result, 0, $limit - 1);
1087  $result[] = implode($delimiter, $lastElements);
1088  }
1089  }
1090  return $result;
1091  }
1092 
1107  public static function revExplode($delimiter, $string, $count = 0)
1108  {
1109  // 2 is the (currently, as of 2014-02) most-used value for $count in the core, therefore we check it first
1110  if ($count === 2) {
1111  $position = strrpos($string, strrev($delimiter));
1112  if ($position !== false) {
1113  return [substr($string, 0, $position), substr($string, $position + strlen($delimiter))];
1114  }
1115  return [$string];
1116  }
1117  if ($count <= 1) {
1118  return [$string];
1119  }
1120  $explodedValues = explode($delimiter, strrev($string), $count);
1121  $explodedValues = array_map('strrev', $explodedValues);
1122  return array_reverse($explodedValues);
1123  }
1124 
1137  public static function trimExplode($delim, $string, $removeEmptyValues = false, $limit = 0)
1138  {
1139  $result = explode($delim, $string);
1140  if ($removeEmptyValues) {
1141  $temp = [];
1142  foreach ($result as $value) {
1143  if (trim($value) !== '') {
1144  $temp[] = $value;
1145  }
1146  }
1147  $result = $temp;
1148  }
1149  if ($limit > 0 && count($result) > $limit) {
1150  $lastElements = array_splice($result, $limit - 1);
1151  $result[] = implode($delim, $lastElements);
1152  } elseif ($limit < 0) {
1153  $result = array_slice($result, 0, $limit);
1154  }
1155  $result = array_map('trim', $result);
1156  return $result;
1157  }
1158 
1170  public static function implodeArrayForUrl($name, array $theArray, $str = '', $skipBlank = false, $rawurlencodeParamName = false)
1171  {
1172  foreach ($theArray as $Akey => $AVal) {
1173  $thisKeyName = $name ? $name . '[' . $Akey . ']' : $Akey;
1174  if (is_array($AVal)) {
1175  $str = self::implodeArrayForUrl($thisKeyName, $AVal, $str, $skipBlank, $rawurlencodeParamName);
1176  } else {
1177  if (!$skipBlank || (string)$AVal !== '') {
1178  $str .= '&' . ($rawurlencodeParamName ? rawurlencode($thisKeyName) : $thisKeyName) . '=' . rawurlencode($AVal);
1179  }
1180  }
1181  }
1182  return $str;
1183  }
1184 
1201  public static function explodeUrl2Array($string, $multidim = null)
1202  {
1203  ‪$output = [];
1204  if ($multidim) {
1205  trigger_error('GeneralUtility::explodeUrl2Array() with a multi-dimensional explode functionality will be removed in TYPO3 v10.0. is built-in PHP with "parse_str($input, $output);". Use the native PHP methods instead.', E_USER_DEPRECATED);
1206  parse_str($string, ‪$output);
1207  } else {
1208  if ($multidim !== null) {
1209  trigger_error('GeneralUtility::explodeUrl2Array() does not need a second method argument anymore, and will be removed in TYPO3 v10.0.', E_USER_DEPRECATED);
1210  }
1211  $p = explode('&', $string);
1212  foreach ($p as $v) {
1213  if ($v !== '') {
1214  list($pK, $pV) = explode('=', $v, 2);
1215  ‪$output[rawurldecode($pK)] = rawurldecode($pV);
1216  }
1217  }
1218  }
1219  return ‪$output;
1220  }
1221 
1231  public static function compileSelectedGetVarsFromArray($varList, array $getArray, $GPvarAlt = true)
1232  {
1233  $keys = self::trimExplode(',', $varList, true);
1234  $outArr = [];
1235  foreach ($keys as $v) {
1236  if (isset($getArray[$v])) {
1237  $outArr[$v] = $getArray[$v];
1238  } elseif ($GPvarAlt) {
1239  $outArr[$v] = self::_GP($v);
1240  }
1241  }
1242  return $outArr;
1243  }
1244 
1252  public static function removeDotsFromTS(array $ts)
1253  {
1254  $out = [];
1255  foreach ($ts as $key => $value) {
1256  if (is_array($value)) {
1257  $key = rtrim($key, '.');
1258  $out[$key] = self::removeDotsFromTS($value);
1259  } else {
1260  $out[$key] = $value;
1261  }
1262  }
1263  return $out;
1264  }
1265 
1266  /*************************
1267  *
1268  * HTML/XML PROCESSING
1269  *
1270  *************************/
1280  public static function get_tag_attributes($tag, bool $decodeEntities = false)
1281  {
1282  $components = self::split_tag_attributes($tag);
1283  // Attribute name is stored here
1284  $name = '';
1285  $valuemode = false;
1286  $attributes = [];
1287  foreach ($components as $key => $val) {
1288  // 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
1289  if ($val !== '=') {
1290  if ($valuemode) {
1291  if ($name) {
1292  $attributes[$name] = $decodeEntities ? htmlspecialchars_decode($val) : $val;
1293  $name = '';
1294  }
1295  } else {
1296  if ($key = strtolower(preg_replace('/[^[:alnum:]_\\:\\-]/', '', $val))) {
1297  $attributes[$key] = '';
1298  $name = $key;
1299  }
1300  }
1301  $valuemode = false;
1302  } else {
1303  $valuemode = true;
1304  }
1305  }
1306  return $attributes;
1307  }
1308 
1316  public static function split_tag_attributes($tag)
1317  {
1318  $tag_tmp = trim(preg_replace('/^<[^[:space:]]*/', '', trim($tag)));
1319  // Removes any > in the end of the string
1320  $tag_tmp = trim(rtrim($tag_tmp, '>'));
1321  $value = [];
1322  // Compared with empty string instead , 030102
1323  while ($tag_tmp !== '') {
1324  $firstChar = $tag_tmp[0];
1325  if ($firstChar === '"' || $firstChar === '\'') {
1326  $reg = explode($firstChar, $tag_tmp, 3);
1327  $value[] = $reg[1];
1328  $tag_tmp = trim($reg[2]);
1329  } elseif ($firstChar === '=') {
1330  $value[] = '=';
1331  // Removes = chars.
1332  $tag_tmp = trim(substr($tag_tmp, 1));
1333  } else {
1334  // There are '' around the value. We look for the next ' ' or '>'
1335  $reg = preg_split('/[[:space:]=]/', $tag_tmp, 2);
1336  $value[] = trim($reg[0]);
1337  $tag_tmp = trim(substr($tag_tmp, strlen($reg[0]), 1) . ($reg[1] ?? ''));
1338  }
1339  }
1340  reset($value);
1341  return $value;
1342  }
1343 
1352  public static function implodeAttributes(array $arr, $xhtmlSafe = false, $dontOmitBlankAttribs = false)
1353  {
1354  if ($xhtmlSafe) {
1355  $newArr = [];
1356  foreach ($arr as $p => $v) {
1357  if (!isset($newArr[strtolower($p)])) {
1358  $newArr[strtolower($p)] = htmlspecialchars($v);
1359  }
1360  }
1361  $arr = $newArr;
1362  }
1363  $list = [];
1364  foreach ($arr as $p => $v) {
1365  if ((string)$v !== '' || $dontOmitBlankAttribs) {
1366  $list[] = $p . '="' . $v . '"';
1367  }
1368  }
1369  return implode(' ', $list);
1370  }
1371 
1380  public static function wrapJS($string)
1381  {
1382  if (trim($string)) {
1383  // remove nl from the beginning
1384  $string = ltrim($string, LF);
1385  // re-ident to one tab using the first line as reference
1386  $match = [];
1387  if (preg_match('/^(\\t+)/', $string, $match)) {
1388  $string = str_replace($match[1], "\t", $string);
1389  }
1390  return '<script type="text/javascript">
1391 /*<![CDATA[*/
1392 ' . $string . '
1393 /*]]>*/
1394 </script>';
1395  }
1396  return '';
1397  }
1398 
1407  public static function xml2tree($string, $depth = 999, $parserOptions = [])
1408  {
1409  // Disables the functionality to allow external entities to be loaded when parsing the XML, must be kept
1410  $previousValueOfEntityLoader = libxml_disable_entity_loader(true);
1411  ‪$parser = xml_parser_create();
1412  $vals = [];
1413  $index = [];
1414  xml_parser_set_option(‪$parser, XML_OPTION_CASE_FOLDING, 0);
1415  xml_parser_set_option(‪$parser, XML_OPTION_SKIP_WHITE, 0);
1416  foreach ($parserOptions as $option => $value) {
1417  xml_parser_set_option(‪$parser, $option, $value);
1418  }
1419  xml_parse_into_struct(‪$parser, $string, $vals, $index);
1420  libxml_disable_entity_loader($previousValueOfEntityLoader);
1421  if (xml_get_error_code(‪$parser)) {
1422  return 'Line ' . xml_get_current_line_number(‪$parser) . ': ' . xml_error_string(xml_get_error_code(‪$parser));
1423  }
1424  xml_parser_free(‪$parser);
1425  $stack = [[]];
1426  $stacktop = 0;
1427  $startPoint = 0;
1428  $tagi = [];
1429  foreach ($vals as $key => $val) {
1430  $type = $val['type'];
1431  // open tag:
1432  if ($type === 'open' || $type === 'complete') {
1433  $stack[$stacktop++] = $tagi;
1434  if ($depth == $stacktop) {
1435  $startPoint = $key;
1436  }
1437  $tagi = ['tag' => $val['tag']];
1438  if (isset($val['attributes'])) {
1439  $tagi['attrs'] = $val['attributes'];
1440  }
1441  if (isset($val['value'])) {
1442  $tagi['values'][] = $val['value'];
1443  }
1444  }
1445  // finish tag:
1446  if ($type === 'complete' || $type === 'close') {
1447  $oldtagi = $tagi;
1448  $tagi = $stack[--$stacktop];
1449  $oldtag = $oldtagi['tag'];
1450  unset($oldtagi['tag']);
1451  if ($depth == $stacktop + 1) {
1452  if ($key - $startPoint > 0) {
1453  $partArray = array_slice($vals, $startPoint + 1, $key - $startPoint - 1);
1454  $oldtagi['XMLvalue'] = self::xmlRecompileFromStructValArray($partArray);
1455  } else {
1456  $oldtagi['XMLvalue'] = $oldtagi['values'][0];
1457  }
1458  }
1459  $tagi['ch'][$oldtag][] = $oldtagi;
1460  unset($oldtagi);
1461  }
1462  // cdata
1463  if ($type === 'cdata') {
1464  $tagi['values'][] = $val['value'];
1465  }
1466  }
1467  return $tagi['ch'];
1468  }
1469 
1490  public static function array2xml(array $array, $NSprefix = '', $level = 0, $docTag = 'phparray', $spaceInd = 0, array $options = [], array $stackData = [])
1491  {
1492  // 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
1493  $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);
1494  // Set indenting mode:
1495  $indentChar = $spaceInd ? ' ' : "\t";
1496  $indentN = $spaceInd > 0 ? $spaceInd : 1;
1497  $nl = $spaceInd >= 0 ? LF : '';
1498  // Init output variable:
1499  ‪$output = '';
1500  // Traverse the input array
1501  foreach ($array as $k => $v) {
1502  $attr = '';
1503  $tagName = $k;
1504  // Construct the tag name.
1505  // Use tag based on grand-parent + parent tag name
1506  if (isset($stackData['grandParentTagName'], $stackData['parentTagName'], $options['grandParentTagMap'][$stackData['grandParentTagName'] . '/' . $stackData['parentTagName']])) {
1507  $attr .= ' index="' . htmlspecialchars($tagName) . '"';
1508  $tagName = (string)$options['grandParentTagMap'][$stackData['grandParentTagName'] . '/' . $stackData['parentTagName']];
1509  } elseif (isset($stackData['parentTagName'], $options['parentTagMap'][$stackData['parentTagName'] . ':_IS_NUM']) && ‪MathUtility::canBeInterpretedAsInteger($tagName)) {
1510  // Use tag based on parent tag name + if current tag is numeric
1511  $attr .= ' index="' . htmlspecialchars($tagName) . '"';
1512  $tagName = (string)$options['parentTagMap'][$stackData['parentTagName'] . ':_IS_NUM'];
1513  } elseif (isset($stackData['parentTagName'], $options['parentTagMap'][$stackData['parentTagName'] . ':' . $tagName])) {
1514  // Use tag based on parent tag name + current tag
1515  $attr .= ' index="' . htmlspecialchars($tagName) . '"';
1516  $tagName = (string)$options['parentTagMap'][$stackData['parentTagName'] . ':' . $tagName];
1517  } elseif (isset($stackData['parentTagName'], $options['parentTagMap'][$stackData['parentTagName']])) {
1518  // Use tag based on parent tag name:
1519  $attr .= ' index="' . htmlspecialchars($tagName) . '"';
1520  $tagName = (string)$options['parentTagMap'][$stackData['parentTagName']];
1521  } elseif (‪MathUtility::canBeInterpretedAsInteger($tagName)) {
1522  // If integer...;
1523  if ($options['useNindex']) {
1524  // If numeric key, prefix "n"
1525  $tagName = 'n' . $tagName;
1526  } else {
1527  // Use special tag for num. keys:
1528  $attr .= ' index="' . $tagName . '"';
1529  $tagName = $options['useIndexTagForNum'] ?: 'numIndex';
1530  }
1531  } elseif (!empty($options['useIndexTagForAssoc'])) {
1532  // Use tag for all associative keys:
1533  $attr .= ' index="' . htmlspecialchars($tagName) . '"';
1534  $tagName = $options['useIndexTagForAssoc'];
1535  }
1536  // The tag name is cleaned up so only alphanumeric chars (plus - and _) are in there and not longer than 100 chars either.
1537  $tagName = substr(preg_replace('/[^[:alnum:]_-]/', '', $tagName), 0, 100);
1538  // If the value is an array then we will call this function recursively:
1539  if (is_array($v)) {
1540  // Sub elements:
1541  if (isset($options['alt_options']) && $options['alt_options'][($stackData['path'] ?? '') . '/' . $tagName]) {
1542  $subOptions = $options['alt_options'][$stackData['path'] . '/' . $tagName];
1543  $clearStackPath = $subOptions['clearStackPath'];
1544  } else {
1545  $subOptions = $options;
1546  $clearStackPath = false;
1547  }
1548  if (empty($v)) {
1549  $content = '';
1550  } else {
1551  $content = $nl . self::array2xml($v, $NSprefix, $level + 1, '', $spaceInd, $subOptions, [
1552  'parentTagName' => $tagName,
1553  'grandParentTagName' => $stackData['parentTagName'] ?? '',
1554  'path' => $clearStackPath ? '' : ($stackData['path'] ?? '') . '/' . $tagName
1555  ]) . ($spaceInd >= 0 ? str_pad('', ($level + 1) * $indentN, $indentChar) : '');
1556  }
1557  // Do not set "type = array". Makes prettier XML but means that empty arrays are not restored with xml2array
1558  if (!isset($options['disableTypeAttrib']) || (int)$options['disableTypeAttrib'] != 2) {
1559  $attr .= ' type="array"';
1560  }
1561  } else {
1562  // Just a value:
1563  // Look for binary chars:
1564  $vLen = strlen($v);
1565  // Go for base64 encoding if the initial segment NOT matching any binary char has the same length as the whole string!
1566  if ($vLen && strcspn($v, $binaryChars) != $vLen) {
1567  // If the value contained binary chars then we base64-encode it an set an attribute to notify this situation:
1568  $content = $nl . chunk_split(base64_encode($v));
1569  $attr .= ' base64="1"';
1570  } else {
1571  // Otherwise, just htmlspecialchar the stuff:
1572  $content = htmlspecialchars($v);
1573  $dType = gettype($v);
1574  if ($dType === 'string') {
1575  if (isset($options['useCDATA']) && $options['useCDATA'] && $content != $v) {
1576  $content = '<![CDATA[' . $v . ']]>';
1577  }
1578  } elseif (!$options['disableTypeAttrib']) {
1579  $attr .= ' type="' . $dType . '"';
1580  }
1581  }
1582  }
1583  if ((string)$tagName !== '') {
1584  // Add the element to the output string:
1585  ‪$output .= ($spaceInd >= 0 ? str_pad('', ($level + 1) * $indentN, $indentChar) : '')
1586  . '<' . $NSprefix . $tagName . $attr . '>' . $content . '</' . $NSprefix . $tagName . '>' . $nl;
1587  }
1588  }
1589  // If we are at the outer-most level, then we finally wrap it all in the document tags and return that as the value:
1590  if (!$level) {
1591  ‪$output = '<' . $docTag . '>' . $nl . ‪$output . '</' . $docTag . '>';
1592  }
1593  return ‪$output;
1594  }
1595 
1607  public static function xml2array($string, $NSprefix = '', $reportDocTag = false)
1608  {
1609  $runtimeCache = static::makeInstance(CacheManager::class)->getCache('cache_runtime');
1610  $firstLevelCache = $runtimeCache->get('generalUtilityXml2Array') ?: [];
1611  $identifier = md5($string . $NSprefix . ($reportDocTag ? '1' : '0'));
1612  // Look up in first level cache
1613  if (empty($firstLevelCache[$identifier])) {
1614  $firstLevelCache[$identifier] = self::xml2arrayProcess(trim($string), $NSprefix, $reportDocTag);
1615  $runtimeCache->set('generalUtilityXml2Array', $firstLevelCache);
1616  }
1617  return $firstLevelCache[$identifier];
1618  }
1619 
1630  protected static function xml2arrayProcess($string, $NSprefix = '', $reportDocTag = false)
1631  {
1632  // Disables the functionality to allow external entities to be loaded when parsing the XML, must be kept
1633  $previousValueOfEntityLoader = libxml_disable_entity_loader(true);
1634  // Create parser:
1635  ‪$parser = xml_parser_create();
1636  $vals = [];
1637  $index = [];
1638  xml_parser_set_option(‪$parser, XML_OPTION_CASE_FOLDING, 0);
1639  xml_parser_set_option(‪$parser, XML_OPTION_SKIP_WHITE, 0);
1640  // Default output charset is UTF-8, only ASCII, ISO-8859-1 and UTF-8 are supported!!!
1641  $match = [];
1642  preg_match('/^[[:space:]]*<\\?xml[^>]*encoding[[:space:]]*=[[:space:]]*"([^"]*)"/', substr($string, 0, 200), $match);
1643  $theCharset = $match[1] ?? 'utf-8';
1644  // us-ascii / utf-8 / iso-8859-1
1645  xml_parser_set_option(‪$parser, XML_OPTION_TARGET_ENCODING, $theCharset);
1646  // Parse content:
1647  xml_parse_into_struct(‪$parser, $string, $vals, $index);
1648  libxml_disable_entity_loader($previousValueOfEntityLoader);
1649  // If error, return error message:
1650  if (xml_get_error_code(‪$parser)) {
1651  return 'Line ' . xml_get_current_line_number(‪$parser) . ': ' . xml_error_string(xml_get_error_code(‪$parser));
1652  }
1653  xml_parser_free(‪$parser);
1654  // Init vars:
1655  $stack = [[]];
1656  $stacktop = 0;
1657  $current = [];
1658  $tagName = '';
1659  $documentTag = '';
1660  // Traverse the parsed XML structure:
1661  foreach ($vals as $key => $val) {
1662  // First, process the tag-name (which is used in both cases, whether "complete" or "close")
1663  $tagName = $val['tag'];
1664  if (!$documentTag) {
1665  $documentTag = $tagName;
1666  }
1667  // Test for name space:
1668  $tagName = $NSprefix && strpos($tagName, $NSprefix) === 0 ? substr($tagName, strlen($NSprefix)) : $tagName;
1669  // Test for numeric tag, encoded on the form "nXXX":
1670  $testNtag = substr($tagName, 1);
1671  // Closing tag.
1672  $tagName = $tagName[0] === 'n' && ‪MathUtility::canBeInterpretedAsInteger($testNtag) ? (int)$testNtag : $tagName;
1673  // Test for alternative index value:
1674  if ((string)($val['attributes']['index'] ?? '') !== '') {
1675  $tagName = $val['attributes']['index'];
1676  }
1677  // Setting tag-values, manage stack:
1678  switch ($val['type']) {
1679  case 'open':
1680  // If open tag it means there is an array stored in sub-elements. Therefore increase the stackpointer and reset the accumulation array:
1681  // Setting blank place holder
1682  $current[$tagName] = [];
1683  $stack[$stacktop++] = $current;
1684  $current = [];
1685  break;
1686  case 'close':
1687  // If the tag is "close" then it is an array which is closing and we decrease the stack pointer.
1688  $oldCurrent = $current;
1689  $current = $stack[--$stacktop];
1690  // Going to the end of array to get placeholder key, key($current), and fill in array next:
1691  end($current);
1692  $current[key($current)] = $oldCurrent;
1693  unset($oldCurrent);
1694  break;
1695  case 'complete':
1696  // If "complete", then it's a value. If the attribute "base64" is set, then decode the value, otherwise just set it.
1697  if (!empty($val['attributes']['base64'])) {
1698  $current[$tagName] = base64_decode($val['value']);
1699  } else {
1700  // Had to cast it as a string - otherwise it would be evaluate FALSE if tested with isset()!!
1701  $current[$tagName] = (string)($val['value'] ?? '');
1702  // Cast type:
1703  switch ((string)($val['attributes']['type'] ?? '')) {
1704  case 'integer':
1705  $current[$tagName] = (int)$current[$tagName];
1706  break;
1707  case 'double':
1708  $current[$tagName] = (double)$current[$tagName];
1709  break;
1710  case 'boolean':
1711  $current[$tagName] = (bool)$current[$tagName];
1712  break;
1713  case 'NULL':
1714  $current[$tagName] = null;
1715  break;
1716  case 'array':
1717  // 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...
1718  $current[$tagName] = [];
1719  break;
1720  }
1721  }
1722  break;
1723  }
1724  }
1725  if ($reportDocTag) {
1726  $current[$tagName]['_DOCUMENT_TAG'] = $documentTag;
1727  }
1728  // Finally return the content of the document tag.
1729  return $current[$tagName];
1730  }
1731 
1738  public static function xmlRecompileFromStructValArray(array $vals)
1739  {
1740  $XMLcontent = '';
1741  foreach ($vals as $val) {
1742  $type = $val['type'];
1743  // Open tag:
1744  if ($type === 'open' || $type === 'complete') {
1745  $XMLcontent .= '<' . $val['tag'];
1746  if (isset($val['attributes'])) {
1747  foreach ($val['attributes'] as $k => $v) {
1748  $XMLcontent .= ' ' . $k . '="' . htmlspecialchars($v) . '"';
1749  }
1750  }
1751  if ($type === 'complete') {
1752  if (isset($val['value'])) {
1753  $XMLcontent .= '>' . htmlspecialchars($val['value']) . '</' . $val['tag'] . '>';
1754  } else {
1755  $XMLcontent .= '/>';
1756  }
1757  } else {
1758  $XMLcontent .= '>';
1759  }
1760  if ($type === 'open' && isset($val['value'])) {
1761  $XMLcontent .= htmlspecialchars($val['value']);
1762  }
1763  }
1764  // Finish tag:
1765  if ($type === 'close') {
1766  $XMLcontent .= '</' . $val['tag'] . '>';
1767  }
1768  // Cdata
1769  if ($type === 'cdata') {
1770  $XMLcontent .= htmlspecialchars($val['value']);
1771  }
1772  }
1773  return $XMLcontent;
1774  }
1775 
1783  public static function minifyJavaScript($script, &$error = '')
1784  {
1785  $fakeThis = false;
1786  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_div.php']['minifyJavaScript'] ?? [] as $hookMethod) {
1787  try {
1788  $parameters = ['script' => $script];
1789  $script = static::callUserFunction($hookMethod, $parameters, $fakeThis);
1790  } catch (\Exception $e) {
1791  $errorMessage = 'Error minifying java script: ' . $e->getMessage();
1792  $error .= $errorMessage;
1793  static::getLogger()->warning($errorMessage, [
1794  'JavaScript' => $script,
1795  'hook' => $hookMethod,
1796  'exception' => $e,
1797  ]);
1798  }
1799  }
1800  return $script;
1801  }
1802 
1803  /*************************
1804  *
1805  * FILES FUNCTIONS
1806  *
1807  *************************/
1818  public static function getUrl($url, $includeHeader = 0, $requestHeaders = null, &$report = null)
1819  {
1820  if (isset($report)) {
1821  $report['error'] = 0;
1822  $report['message'] = '';
1823  }
1824  // Looks like it's an external file, use Guzzle by default
1825  if (preg_match('/^(?:http|ftp)s?|s(?:ftp|cp):/', $url)) {
1827  $requestFactory = static::makeInstance(RequestFactory::class);
1828  if (is_array($requestHeaders)) {
1829  // Check is $requestHeaders is an associative array or not
1830  if (count(array_filter(array_keys($requestHeaders), 'is_string')) === 0) {
1831  trigger_error('Request headers as colon-separated string will stop working in TYPO3 v10.0, use an associative array instead.', E_USER_DEPRECATED);
1832  // Convert cURL style lines of headers to Guzzle key/value(s) pairs.
1833  $requestHeaders = static::splitHeaderLines($requestHeaders);
1834  }
1835  $configuration = ['headers' => $requestHeaders];
1836  } else {
1837  $configuration = [];
1838  }
1839  $includeHeader = (int)$includeHeader;
1840  $method = $includeHeader === 2 ? 'HEAD' : 'GET';
1841  try {
1842  if (isset($report)) {
1843  $report['lib'] = 'GuzzleHttp';
1844  }
1845  $response = $requestFactory->request($url, $method, $configuration);
1846  } catch (RequestException $exception) {
1847  if (isset($report)) {
1848  $report['error'] = $exception->getCode() ?: 1518707554;
1849  $report['message'] = $exception->getMessage();
1850  $report['exception'] = $exception;
1851  }
1852  return false;
1853  }
1854  $content = '';
1855  // Add the headers to the output
1856  if ($includeHeader) {
1857  $parsedURL = parse_url($url);
1858  $content = $method . ' ' . ($parsedURL['path'] ?? '/')
1859  . (!empty($parsedURL['query']) ? '?' . $parsedURL['query'] : '') . ' HTTP/1.0' . CRLF
1860  . 'Host: ' . $parsedURL['host'] . CRLF
1861  . 'Connection: close' . CRLF;
1862  if (is_array($requestHeaders)) {
1863  $content .= implode(CRLF, $requestHeaders) . CRLF;
1864  }
1865  foreach ($response->getHeaders() as $headerName => $headerValues) {
1866  $content .= $headerName . ': ' . implode(', ', $headerValues) . CRLF;
1867  }
1868  // Headers are separated from the body with two CRLFs
1869  $content .= CRLF;
1870  }
1871 
1872  $content .= $response->getBody()->getContents();
1873 
1874  if (isset($report)) {
1875  if ($response->getStatusCode() >= 300 && $response->getStatusCode() < 400) {
1876  $report['http_code'] = $response->getStatusCode();
1877  $report['content_type'] = $response->getHeaderLine('Content-Type');
1878  $report['error'] = $response->getStatusCode();
1879  $report['message'] = $response->getReasonPhrase();
1880  } elseif (empty($content)) {
1881  $report['error'] = $response->getStatusCode();
1882  $report['message'] = $response->getReasonPhrase();
1883  } elseif ($includeHeader) {
1884  // Set only for $includeHeader to work exactly like PHP variant
1885  $report['http_code'] = $response->getStatusCode();
1886  $report['content_type'] = $response->getHeaderLine('Content-Type');
1887  }
1888  }
1889  } else {
1890  if (isset($report)) {
1891  $report['lib'] = 'file';
1892  }
1893  $content = @file_get_contents($url);
1894  if ($content === false && isset($report)) {
1895  $report['error'] = -1;
1896  $report['message'] = 'Couldn\'t get URL: ' . $url;
1897  }
1898  }
1899  return $content;
1900  }
1901 
1910  protected static function splitHeaderLines(array $headers): array
1911  {
1912  $newHeaders = [];
1913  foreach ($headers as $header) {
1914  $parts = preg_split('/:[ \t]*/', $header, 2, PREG_SPLIT_NO_EMPTY);
1915  if (count($parts) !== 2) {
1916  continue;
1917  }
1918  $key = &$parts[0];
1919  $value = &$parts[1];
1920  if (array_key_exists($key, $newHeaders)) {
1921  if (is_array($newHeaders[$key])) {
1922  $newHeaders[$key][] = $value;
1923  } else {
1924  $prevValue = &$newHeaders[$key];
1925  $newHeaders[$key] = [$prevValue, $value];
1926  }
1927  } else {
1928  $newHeaders[$key] = $value;
1929  }
1930  }
1931  return $newHeaders;
1932  }
1933 
1942  public static function writeFile($file, $content, $changePermissions = false)
1943  {
1944  if (!@is_file($file)) {
1945  $changePermissions = true;
1946  }
1947  if ($fd = fopen($file, 'wb')) {
1948  $res = fwrite($fd, $content);
1949  fclose($fd);
1950  if ($res === false) {
1951  return false;
1952  }
1953  // Change the permissions only if the file has just been created
1954  if ($changePermissions) {
1955  static::fixPermissions($file);
1956  }
1957  return true;
1958  }
1959  return false;
1960  }
1961 
1969  public static function fixPermissions($path, $recursive = false)
1970  {
1971  if (‪Environment::isWindows()) {
1972  return true;
1973  }
1974  $result = false;
1975  // Make path absolute
1976  if (!static::isAbsPath($path)) {
1977  $path = static::getFileAbsFileName($path);
1978  }
1979  if (static::isAllowedAbsPath($path)) {
1980  if (@is_file($path)) {
1981  $targetPermissions = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['fileCreateMask'] ?? '0644';
1982  } elseif (@is_dir($path)) {
1983  $targetPermissions = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask'] ?? '0755';
1984  }
1985  if (!empty($targetPermissions)) {
1986  // make sure it's always 4 digits
1987  $targetPermissions = str_pad($targetPermissions, 4, 0, STR_PAD_LEFT);
1988  $targetPermissions = octdec($targetPermissions);
1989  // "@" is there because file is not necessarily OWNED by the user
1990  $result = @chmod($path, $targetPermissions);
1991  }
1992  // Set createGroup if not empty
1993  if (
1994  isset(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['createGroup'])
1995  && ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['createGroup'] !== ''
1996  ) {
1997  // "@" is there because file is not necessarily OWNED by the user
1998  $changeGroupResult = @chgrp($path, ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['createGroup']);
1999  $result = $changeGroupResult ? $result : false;
2000  }
2001  // Call recursive if recursive flag if set and $path is directory
2002  if ($recursive && @is_dir($path)) {
2003  $handle = opendir($path);
2004  if (is_resource($handle)) {
2005  while (($file = readdir($handle)) !== false) {
2006  $recursionResult = null;
2007  if ($file !== '.' && $file !== '..') {
2008  if (@is_file($path . '/' . $file)) {
2009  $recursionResult = static::fixPermissions($path . '/' . $file);
2010  } elseif (@is_dir($path . '/' . $file)) {
2011  $recursionResult = static::fixPermissions($path . '/' . $file, true);
2012  }
2013  if (isset($recursionResult) && !$recursionResult) {
2014  $result = false;
2015  }
2016  }
2017  }
2018  closedir($handle);
2019  }
2020  }
2021  }
2022  return $result;
2023  }
2024 
2033  public static function writeFileToTypo3tempDir($filepath, $content)
2034  {
2035  // Parse filepath into directory and basename:
2036  $fI = pathinfo($filepath);
2037  $fI['dirname'] .= '/';
2038  // Check parts:
2039  if (!static::validPathStr($filepath) || !$fI['basename'] || strlen($fI['basename']) >= 60) {
2040  return 'Input filepath "' . $filepath . '" was generally invalid!';
2041  }
2042 
2043  // Setting main temporary directory name (standard)
2044  $allowedPathPrefixes = [
2045  ‪Environment::getPublicPath() . '/typo3temp' => 'Environment::getPublicPath() + "/typo3temp/"'
2046  ];
2047  // Also allow project-path + /var/
2048  if (‪Environment::getVarPath() !== ‪Environment::getPublicPath() . '/typo3temp/var') {
2049  $relPath = substr(‪Environment::getVarPath(), strlen(‪Environment::getProjectPath()) + 1);
2050  $allowedPathPrefixes[‪Environment::getVarPath()] = 'ProjectPath + ' . $relPath;
2051  }
2052 
2053  $errorMessage = null;
2054  foreach ($allowedPathPrefixes as $pathPrefix => $prefixLabel) {
2055  $dirName = $pathPrefix . '/';
2056  // Invalid file path, let's check for the other path, if it exists
2057  if (!static::isFirstPartOfStr($fI['dirname'], $dirName)) {
2058  if ($errorMessage === null) {
2059  $errorMessage = '"' . $fI['dirname'] . '" was not within directory ' . $prefixLabel;
2060  }
2061  continue;
2062  }
2063  // This resets previous error messages from the first path
2064  $errorMessage = null;
2065 
2066  if (!@is_dir($dirName)) {
2067  $errorMessage = $prefixLabel . ' was not a directory!';
2068  // continue and see if the next iteration resets the errorMessage above
2069  continue;
2070  }
2071  // Checking if the "subdir" is found
2072  $subdir = substr($fI['dirname'], strlen($dirName));
2073  if ($subdir) {
2074  if (preg_match('#^(?:[[:alnum:]_]+/)+$#', $subdir)) {
2075  $dirName .= $subdir;
2076  if (!@is_dir($dirName)) {
2077  static::mkdir_deep($pathPrefix . '/' . $subdir);
2078  }
2079  } else {
2080  $errorMessage = 'Subdir, "' . $subdir . '", was NOT on the form "[[:alnum:]_]/+"';
2081  break;
2082  }
2083  }
2084  // Checking dir-name again (sub-dir might have been created)
2085  if (@is_dir($dirName)) {
2086  if ($filepath === $dirName . $fI['basename']) {
2087  static::writeFile($filepath, $content);
2088  if (!@is_file($filepath)) {
2089  $errorMessage = 'The file was not written to the disk. Please, check that you have write permissions to the ' . $prefixLabel . ' directory.';
2090  break;
2091  }
2092  } else {
2093  $errorMessage = 'Calculated file location didn\'t match input "' . $filepath . '".';
2094  break;
2095  }
2096  } else {
2097  $errorMessage = '"' . $dirName . '" is not a directory!';
2098  break;
2099  }
2100  }
2101  return $errorMessage;
2102  }
2103 
2112  public static function mkdir($newFolder)
2113  {
2114  $result = @mkdir($newFolder, octdec(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask']));
2115  if ($result) {
2116  static::fixPermissions($newFolder);
2117  }
2118  return $result;
2119  }
2120 
2130  public static function mkdir_deep($directory, $deepDirectory = '')
2131  {
2132  if (!is_string($directory)) {
2133  throw new \InvalidArgumentException('The specified directory is of type "' . gettype($directory) . '" but a string is expected.', 1303662955);
2134  }
2135  if (!is_string($deepDirectory)) {
2136  throw new \InvalidArgumentException('The specified directory is of type "' . gettype($deepDirectory) . '" but a string is expected.', 1303662956);
2137  }
2138  // Ensure there is only one slash
2139  $fullPath = rtrim($directory, '/') . '/';
2140  if ($deepDirectory !== '') {
2141  trigger_error('Second argument $deepDirectory of GeneralUtility::mkdir_deep() will be removed in TYPO3 v10.0, use a combined string as first argument instead.', E_USER_DEPRECATED);
2142  $fullPath .= ltrim($deepDirectory, '/');
2143  }
2144  if ($fullPath !== '/' && !is_dir($fullPath)) {
2145  $firstCreatedPath = static::createDirectoryPath($fullPath);
2146  if ($firstCreatedPath !== '') {
2147  static::fixPermissions($firstCreatedPath, true);
2148  }
2149  }
2150  }
2151 
2163  protected static function createDirectoryPath($fullDirectoryPath)
2164  {
2165  $currentPath = $fullDirectoryPath;
2166  $firstCreatedPath = '';
2167  $permissionMask = octdec(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask']);
2168  if (!@is_dir($currentPath)) {
2169  do {
2170  $firstCreatedPath = $currentPath;
2171  $separatorPosition = strrpos($currentPath, DIRECTORY_SEPARATOR);
2172  $currentPath = substr($currentPath, 0, $separatorPosition);
2173  } while (!is_dir($currentPath) && $separatorPosition !== false);
2174  $result = @mkdir($fullDirectoryPath, $permissionMask, true);
2175  // Check existence of directory again to avoid race condition. Directory could have get created by another process between previous is_dir() and mkdir()
2176  if (!$result && !@is_dir($fullDirectoryPath)) {
2177  throw new \RuntimeException('Could not create directory "' . $fullDirectoryPath . '"!', 1170251401);
2178  }
2179  }
2180  return $firstCreatedPath;
2181  }
2182 
2190  public static function rmdir($path, $removeNonEmpty = false)
2191  {
2192  $OK = false;
2193  // Remove trailing slash
2194  $path = preg_replace('|/$|', '', $path);
2195  $isWindows = DIRECTORY_SEPARATOR === '\\';
2196  if (file_exists($path)) {
2197  $OK = true;
2198  if (!is_link($path) && is_dir($path)) {
2199  if ($removeNonEmpty === true && ($handle = @opendir($path))) {
2200  $entries = [];
2201 
2202  while (false !== ($file = readdir($handle))) {
2203  if ($file === '.' || $file === '..') {
2204  continue;
2205  }
2206 
2207  $entries[] = $path . '/' . $file;
2208  }
2209 
2210  closedir($handle);
2211 
2212  foreach ($entries as $entry) {
2213  if (!static::rmdir($entry, $removeNonEmpty)) {
2214  $OK = false;
2215  }
2216  }
2217  }
2218  if ($OK) {
2219  $OK = @rmdir($path);
2220  }
2221  } elseif (is_link($path) && is_dir($path) && $isWindows) {
2222  $OK = @rmdir($path);
2223  } else {
2224  // If $path is a file, simply remove it
2225  $OK = @unlink($path);
2226  }
2227  clearstatcache();
2228  } elseif (is_link($path)) {
2229  $OK = @unlink($path);
2230  if (!$OK && $isWindows) {
2231  // Try to delete dead folder links on Windows systems
2232  $OK = @rmdir($path);
2233  }
2234  clearstatcache();
2235  }
2236  return $OK;
2237  }
2238 
2249  public static function flushDirectory($directory, $keepOriginalDirectory = false, $flushOpcodeCache = false)
2250  {
2251  $result = false;
2252 
2253  if (is_link($directory)) {
2254  // Avoid attempting to rename the symlink see #87367
2255  $directory = realpath($directory);
2256  }
2257 
2258  if (is_dir($directory)) {
2259  $temporaryDirectory = rtrim($directory, '/') . '.' . ‪StringUtility::getUniqueId('remove');
2260  if (rename($directory, $temporaryDirectory)) {
2261  if ($flushOpcodeCache) {
2262  self::makeInstance(OpcodeCacheService::class)->clearAllActive($directory);
2263  }
2264  if ($keepOriginalDirectory) {
2265  static::mkdir($directory);
2266  }
2267  clearstatcache();
2268  $result = static::rmdir($temporaryDirectory, true);
2269  }
2270  }
2271 
2272  return $result;
2273  }
2274 
2283  public static function get_dirs($path)
2284  {
2285  $dirs = null;
2286  if ($path) {
2287  if (is_dir($path)) {
2288  ‪$dir = scandir($path);
2289  $dirs = [];
2290  foreach (‪$dir as $entry) {
2291  if (is_dir($path . '/' . $entry) && $entry !== '..' && $entry !== '.') {
2292  $dirs[] = $entry;
2293  }
2294  }
2295  } else {
2296  $dirs = 'error';
2297  }
2298  }
2299  return $dirs;
2300  }
2301 
2314  public static function getFilesInDir($path, $extensionList = '', $prependPath = false, $order = '', $excludePattern = '')
2315  {
2316  $excludePattern = (string)$excludePattern;
2317  $path = rtrim($path, '/');
2318  if (!@is_dir($path)) {
2319  return [];
2320  }
2321 
2322  $rawFileList = scandir($path);
2323  if ($rawFileList === false) {
2324  return 'error opening path: "' . $path . '"';
2325  }
2326 
2327  $pathPrefix = $path . '/';
2328  $allowedFileExtensionArray = self::trimExplode(',', $extensionList);
2329  $extensionList = ',' . str_replace(' ', '', $extensionList) . ',';
2330  $files = [];
2331  foreach ($rawFileList as $entry) {
2332  $completePathToEntry = $pathPrefix . $entry;
2333  if (!@is_file($completePathToEntry)) {
2334  continue;
2335  }
2336 
2337  foreach ($allowedFileExtensionArray as $allowedFileExtension) {
2338  if (
2339  ($extensionList === ',,' || stripos($extensionList, ',' . substr($entry, strlen($allowedFileExtension) * -1, strlen($allowedFileExtension)) . ',') !== false)
2340  && ($excludePattern === '' || !preg_match('/^' . $excludePattern . '$/', $entry))
2341  ) {
2342  if ($order !== 'mtime') {
2343  $files[] = $entry;
2344  } else {
2345  // Store the value in the key so we can do a fast asort later.
2346  $files[$entry] = filemtime($completePathToEntry);
2347  }
2348  }
2349  }
2350  }
2351 
2352  $valueName = 'value';
2353  if ($order === 'mtime') {
2354  asort($files);
2355  $valueName = 'key';
2356  }
2357 
2358  $valuePathPrefix = $prependPath ? $pathPrefix : '';
2359  $foundFiles = [];
2360  foreach ($files as $key => $value) {
2361  // Don't change this ever - extensions may depend on the fact that the hash is an md5 of the path! (import/export extension)
2362  $foundFiles[md5($pathPrefix . ${$valueName})] = $valuePathPrefix . ${$valueName};
2363  }
2364 
2365  return $foundFiles;
2366  }
2367 
2379  public static function getAllFilesAndFoldersInPath(array $fileArr, $path, $extList = '', $regDirs = false, $recursivityLevels = 99, $excludePattern = '')
2380  {
2381  if ($regDirs) {
2382  $fileArr[md5($path)] = $path;
2383  }
2384  $fileArr = array_merge($fileArr, self::getFilesInDir($path, $extList, 1, 1, $excludePattern));
2385  $dirs = self::get_dirs($path);
2386  if ($recursivityLevels > 0 && is_array($dirs)) {
2387  foreach ($dirs as $subdirs) {
2388  if ((string)$subdirs !== '' && ($excludePattern === '' || !preg_match('/^' . $excludePattern . '$/', $subdirs))) {
2389  $fileArr = self::getAllFilesAndFoldersInPath($fileArr, $path . $subdirs . '/', $extList, $regDirs, $recursivityLevels - 1, $excludePattern);
2390  }
2391  }
2392  }
2393  return $fileArr;
2394  }
2395 
2403  public static function removePrefixPathFromList(array $fileArr, $prefixToRemove)
2404  {
2405  foreach ($fileArr as $k => &$absFileRef) {
2406  if (self::isFirstPartOfStr($absFileRef, $prefixToRemove)) {
2407  $absFileRef = substr($absFileRef, strlen($prefixToRemove));
2408  } else {
2409  return 'ERROR: One or more of the files was NOT prefixed with the prefix-path!';
2410  }
2411  }
2412  unset($absFileRef);
2413  return $fileArr;
2414  }
2415 
2422  public static function fixWindowsFilePath($theFile)
2423  {
2424  return str_replace(['\\', '//'], '/', $theFile);
2425  }
2426 
2434  public static function resolveBackPath($pathStr)
2435  {
2436  if (strpos($pathStr, '..') === false) {
2437  return $pathStr;
2438  }
2439  $parts = explode('/', $pathStr);
2440  ‪$output = [];
2441  $c = 0;
2442  foreach ($parts as $part) {
2443  if ($part === '..') {
2444  if ($c) {
2445  array_pop(‪$output);
2446  --$c;
2447  } else {
2448  ‪$output[] = $part;
2449  }
2450  } else {
2451  ++$c;
2452  ‪$output[] = $part;
2453  }
2454  }
2455  return implode('/', ‪$output);
2456  }
2457 
2467  public static function locationHeaderUrl($path)
2468  {
2469  if (strpos($path, '//') === 0) {
2470  return $path;
2471  }
2472 
2473  // relative to HOST
2474  if (strpos($path, '/') === 0) {
2475  return self::getIndpEnv('TYPO3_REQUEST_HOST') . $path;
2476  }
2477 
2478  $urlComponents = parse_url($path);
2479  if (!($urlComponents['scheme'] ?? false)) {
2480  // No scheme either
2481  return self::getIndpEnv('TYPO3_REQUEST_DIR') . $path;
2482  }
2483 
2484  return $path;
2485  }
2486 
2494  public static function getMaxUploadFileSize()
2495  {
2496  // Check for PHP restrictions of the maximum size of one of the $_FILES
2497  $phpUploadLimit = self::getBytesFromSizeMeasurement(ini_get('upload_max_filesize'));
2498  // Check for PHP restrictions of the maximum $_POST size
2499  $phpPostLimit = self::getBytesFromSizeMeasurement(ini_get('post_max_size'));
2500  // If the total amount of post data is smaller (!) than the upload_max_filesize directive,
2501  // then this is the real limit in PHP
2502  $phpUploadLimit = $phpPostLimit > 0 && $phpPostLimit < $phpUploadLimit ? $phpPostLimit : $phpUploadLimit;
2503  return floor($phpUploadLimit) / 1024;
2504  }
2505 
2512  public static function getBytesFromSizeMeasurement($measurement)
2513  {
2514  $bytes = (float)$measurement;
2515  if (stripos($measurement, 'G')) {
2516  $bytes *= 1024 * 1024 * 1024;
2517  } elseif (stripos($measurement, 'M')) {
2518  $bytes *= 1024 * 1024;
2519  } elseif (stripos($measurement, 'K')) {
2520  $bytes *= 1024;
2521  }
2522  return $bytes;
2523  }
2524 
2541  public static function createVersionNumberedFilename($file)
2542  {
2543  $lookupFile = explode('?', $file);
2544  $path = self::resolveBackPath(self::dirname(‪Environment::getCurrentScript()) . '/' . $lookupFile[0]);
2545 
2546  $doNothing = false;
2547  if (TYPO3_MODE === 'FE') {
2548  $mode = strtolower(‪$GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['versionNumberInFilename']);
2549  if ($mode === 'embed') {
2550  $mode = true;
2551  } else {
2552  if ($mode === 'querystring') {
2553  $mode = false;
2554  } else {
2555  $doNothing = true;
2556  }
2557  }
2558  } else {
2559  $mode = ‪$GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['versionNumberInFilename'];
2560  }
2561  if ($doNothing || !file_exists($path)) {
2562  // File not found, return filename unaltered
2563  $fullName = $file;
2564  } else {
2565  if (!$mode) {
2566  // If use of .htaccess rule is not configured,
2567  // we use the default query-string method
2568  if (!empty($lookupFile[1])) {
2569  $separator = '&';
2570  } else {
2571  $separator = '?';
2572  }
2573  $fullName = $file . $separator . filemtime($path);
2574  } else {
2575  // Change the filename
2576  $name = explode('.', $lookupFile[0]);
2577  $extension = array_pop($name);
2578  array_push($name, filemtime($path), $extension);
2579  $fullName = implode('.', $name);
2580  // Append potential query string
2581  $fullName .= $lookupFile[1] ? '?' . $lookupFile[1] : '';
2582  }
2583  }
2584  return $fullName;
2585  }
2586 
2594  public static function writeJavaScriptContentToTemporaryFile(string $content)
2595  {
2596  $script = 'typo3temp/assets/js/' . GeneralUtility::shortMD5($content) . '.js';
2597  if (!@is_file(‪Environment::getPublicPath() . '/' . $script)) {
2598  self::writeFileToTypo3tempDir(‪Environment::getPublicPath() . '/' . $script, $content);
2599  }
2600  return $script;
2601  }
2602 
2610  public static function writeStyleSheetContentToTemporaryFile(string $content)
2611  {
2612  $script = 'typo3temp/assets/css/' . self::shortMD5($content) . '.css';
2613  if (!@is_file(‪Environment::getPublicPath() . '/' . $script)) {
2614  self::writeFileToTypo3tempDir(‪Environment::getPublicPath() . '/' . $script, $content);
2615  }
2616  return $script;
2617  }
2618 
2619  /*************************
2620  *
2621  * SYSTEM INFORMATION
2622  *
2623  *************************/
2624 
2633  public static function linkThisScript(array $getParams = [])
2634  {
2635  $parts = self::getIndpEnv('SCRIPT_NAME');
2636  $params = self::_GET();
2637  foreach ($getParams as $key => $value) {
2638  if ($value !== '') {
2639  $params[$key] = $value;
2640  } else {
2641  unset($params[$key]);
2642  }
2643  }
2644  $pString = self::implodeArrayForUrl('', $params);
2645  return $pString ? $parts . '?' . ltrim($pString, '&') : $parts;
2646  }
2647 
2656  public static function linkThisUrl($url, array $getParams = [])
2657  {
2658  $parts = parse_url($url);
2659  $getP = [];
2660  if ($parts['query']) {
2661  parse_str($parts['query'], $getP);
2662  }
2664  $uP = explode('?', $url);
2665  $params = self::implodeArrayForUrl('', $getP);
2666  $outurl = $uP[0] . ($params ? '?' . substr($params, 1) : '');
2667  return $outurl;
2668  }
2669 
2677  public static function setIndpEnv($envName, $value)
2678  {
2679  self::$indpEnvCache[$envName] = $value;
2680  }
2681 
2690  public static function getIndpEnv($getEnvName)
2691  {
2692  if (array_key_exists($getEnvName, self::$indpEnvCache)) {
2693  return self::$indpEnvCache[$getEnvName];
2694  }
2695 
2696  /*
2697  Conventions:
2698  output from parse_url():
2699  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
2700  [scheme] => 'http'
2701  [user] => 'username'
2702  [pass] => 'password'
2703  [host] => '192.168.1.4'
2704  [port] => '8080'
2705  [path] => '/typo3/32/temp/phpcheck/index.php/arg1/arg2/arg3/'
2706  [query] => 'arg1,arg2,arg3&p1=parameter1&p2[key]=value'
2707  [fragment] => 'link1'Further definition: [path_script] = '/typo3/32/temp/phpcheck/index.php'
2708  [path_dir] = '/typo3/32/temp/phpcheck/'
2709  [path_info] = '/arg1/arg2/arg3/'
2710  [path] = [path_script/path_dir][path_info]Keys supported:URI______:
2711  REQUEST_URI = [path]?[query] = /typo3/32/temp/phpcheck/index.php/arg1/arg2/arg3/?arg1,arg2,arg3&p1=parameter1&p2[key]=value
2712  HTTP_HOST = [host][:[port]] = 192.168.1.4:8080
2713  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')!
2714  PATH_INFO = [path_info] = /arg1/arg2/arg3/
2715  QUERY_STRING = [query] = arg1,arg2,arg3&p1=parameter1&p2[key]=value
2716  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
2717  (Notice: NO username/password + NO fragment)CLIENT____:
2718  REMOTE_ADDR = (client IP)
2719  REMOTE_HOST = (client host)
2720  HTTP_USER_AGENT = (client user agent)
2721  HTTP_ACCEPT_LANGUAGE = (client accept language)SERVER____:
2722  SCRIPT_FILENAME = Absolute filename of script (Differs between windows/unix). On windows 'C:\\some\\path\\' will be converted to 'C:/some/path/'Special extras:
2723  TYPO3_HOST_ONLY = [host] = 192.168.1.4
2724  TYPO3_PORT = [port] = 8080 (blank if 80, taken from host value)
2725  TYPO3_REQUEST_HOST = [scheme]://[host][:[port]]
2726  TYPO3_REQUEST_URL = [scheme]://[host][:[port]][path]?[query] (scheme will by default be "http" until we can detect something different)
2727  TYPO3_REQUEST_SCRIPT = [scheme]://[host][:[port]][path_script]
2728  TYPO3_REQUEST_DIR = [scheme]://[host][:[port]][path_dir]
2729  TYPO3_SITE_URL = [scheme]://[host][:[port]][path_dir] of the TYPO3 website frontend
2730  TYPO3_SITE_PATH = [path_dir] of the TYPO3 website frontend
2731  TYPO3_SITE_SCRIPT = [script / Speaking URL] of the TYPO3 website
2732  TYPO3_DOCUMENT_ROOT = Absolute path of root of documents: TYPO3_DOCUMENT_ROOT.SCRIPT_NAME = SCRIPT_FILENAME (typically)
2733  TYPO3_SSL = Returns TRUE if this session uses SSL/TLS (https)
2734  TYPO3_PROXY = Returns TRUE if this session runs over a well known proxyNotice: [fragment] is apparently NEVER available to the script!Testing suggestions:
2735  - Output all the values.
2736  - 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
2737  - ALSO TRY the script from the ROOT of a site (like 'http://www.mytest.com/' and not 'http://www.mytest.com/test/' !!)
2738  */
2739  $retVal = '';
2740  switch ((string)$getEnvName) {
2741  case 'SCRIPT_NAME':
2742  $retVal = self::isRunningOnCgiServerApi()
2743  && (($_SERVER['ORIG_PATH_INFO'] ?? false) ?: ($_SERVER['PATH_INFO'] ?? false))
2744  ? (($_SERVER['ORIG_PATH_INFO'] ?? '') ?: ($_SERVER['PATH_INFO'] ?? ''))
2745  : (($_SERVER['ORIG_SCRIPT_NAME'] ?? '') ?: ($_SERVER['SCRIPT_NAME'] ?? ''));
2746  // Add a prefix if TYPO3 is behind a proxy: ext-domain.com => int-server.com/prefix
2747  if (self::cmpIP($_SERVER['REMOTE_ADDR'] ?? '', ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyIP'] ?? '')) {
2748  if (self::getIndpEnv('TYPO3_SSL') && ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyPrefixSSL']) {
2749  $retVal = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyPrefixSSL'] . $retVal;
2750  } elseif (‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyPrefix']) {
2751  $retVal = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyPrefix'] . $retVal;
2752  }
2753  }
2754  break;
2755  case 'SCRIPT_FILENAME':
2757  break;
2758  case 'REQUEST_URI':
2759  // Typical application of REQUEST_URI is return urls, forms submitting to itself etc. Example: returnUrl='.rawurlencode(\TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('REQUEST_URI'))
2760  if (!empty(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['requestURIvar'])) {
2761  // This is for URL rewriters that store the original URI in a server variable (eg ISAPI_Rewriter for IIS: HTTP_X_REWRITE_URL)
2762  list($v, $n) = explode('|', ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['requestURIvar']);
2763  $retVal = ‪$GLOBALS[$v][$n];
2764  } elseif (empty($_SERVER['REQUEST_URI'])) {
2765  // This is for ISS/CGI which does not have the REQUEST_URI available.
2766  $retVal = '/' . ltrim(self::getIndpEnv('SCRIPT_NAME'), '/') . (!empty($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : '');
2767  } else {
2768  $retVal = '/' . ltrim($_SERVER['REQUEST_URI'], '/');
2769  }
2770  // Add a prefix if TYPO3 is behind a proxy: ext-domain.com => int-server.com/prefix
2771  if (isset($_SERVER['REMOTE_ADDR'], ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyIP'])
2772  && self::cmpIP($_SERVER['REMOTE_ADDR'], ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyIP'])
2773  ) {
2774  if (self::getIndpEnv('TYPO3_SSL') && ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyPrefixSSL']) {
2775  $retVal = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyPrefixSSL'] . $retVal;
2776  } elseif (‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyPrefix']) {
2777  $retVal = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyPrefix'] . $retVal;
2778  }
2779  }
2780  break;
2781  case 'PATH_INFO':
2782  // $_SERVER['PATH_INFO'] != $_SERVER['SCRIPT_NAME'] is necessary because some servers (Windows/CGI)
2783  // are seen to set PATH_INFO equal to script_name
2784  // Further, there must be at least one '/' in the path - else the PATH_INFO value does not make sense.
2785  // IF 'PATH_INFO' never works for our purpose in TYPO3 with CGI-servers,
2786  // then 'PHP_SAPI=='cgi'' might be a better check.
2787  // Right now strcmp($_SERVER['PATH_INFO'], GeneralUtility::getIndpEnv('SCRIPT_NAME')) will always
2788  // return FALSE for CGI-versions, but that is only as long as SCRIPT_NAME is set equal to PATH_INFO
2789  // because of PHP_SAPI=='cgi' (see above)
2790  if (!self::isRunningOnCgiServerApi()) {
2791  $retVal = $_SERVER['PATH_INFO'];
2792  }
2793  break;
2794  case 'TYPO3_REV_PROXY':
2795  $retVal = self::cmpIP($_SERVER['REMOTE_ADDR'], ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyIP']);
2796  break;
2797  case 'REMOTE_ADDR':
2798  $retVal = $_SERVER['REMOTE_ADDR'] ?? null;
2799  if (self::cmpIP($retVal, ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyIP'] ?? '')) {
2800  $ip = self::trimExplode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
2801  // Choose which IP in list to use
2802  if (!empty($ip)) {
2803  switch (‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyHeaderMultiValue']) {
2804  case 'last':
2805  $ip = array_pop($ip);
2806  break;
2807  case 'first':
2808  $ip = array_shift($ip);
2809  break;
2810  case 'none':
2811 
2812  default:
2813  $ip = '';
2814  }
2815  }
2816  if (self::validIP($ip)) {
2817  $retVal = $ip;
2818  }
2819  }
2820  break;
2821  case 'HTTP_HOST':
2822  // if it is not set we're most likely on the cli
2823  $retVal = $_SERVER['HTTP_HOST'] ?? null;
2824  if (isset($_SERVER['REMOTE_ADDR']) && static::cmpIP($_SERVER['REMOTE_ADDR'], ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyIP'])) {
2825  $host = self::trimExplode(',', $_SERVER['HTTP_X_FORWARDED_HOST']);
2826  // Choose which host in list to use
2827  if (!empty($host)) {
2828  switch (‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyHeaderMultiValue']) {
2829  case 'last':
2830  $host = array_pop($host);
2831  break;
2832  case 'first':
2833  $host = array_shift($host);
2834  break;
2835  case 'none':
2836 
2837  default:
2838  $host = '';
2839  }
2840  }
2841  if ($host) {
2842  $retVal = $host;
2843  }
2844  }
2845  if (!static::isAllowedHostHeaderValue($retVal)) {
2846  throw new \UnexpectedValueException(
2847  'The current host header value does not match the configured trusted hosts pattern! Check the pattern defined in $GLOBALS[\'TYPO3_CONF_VARS\'][\'SYS\'][\'trustedHostsPattern\'] and adapt it, if you want to allow the current host header \'' . $retVal . '\' for your installation.',
2848  1396795884
2849  );
2850  }
2851  break;
2852  case 'HTTP_REFERER':
2853 
2854  case 'HTTP_USER_AGENT':
2855 
2856  case 'HTTP_ACCEPT_ENCODING':
2857 
2858  case 'HTTP_ACCEPT_LANGUAGE':
2859 
2860  case 'REMOTE_HOST':
2861 
2862  case 'QUERY_STRING':
2863  $retVal = $_SERVER[$getEnvName] ?? '';
2864  break;
2865  case 'TYPO3_DOCUMENT_ROOT':
2866  // Get the web root (it is not the root of the TYPO3 installation)
2867  // The absolute path of the script can be calculated with TYPO3_DOCUMENT_ROOT + SCRIPT_FILENAME
2868  // 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.
2869  // Therefore the DOCUMENT_ROOT is now always calculated as the SCRIPT_FILENAME minus the end part shared with SCRIPT_NAME.
2870  $SFN = self::getIndpEnv('SCRIPT_FILENAME');
2871  $SN_A = explode('/', strrev(self::getIndpEnv('SCRIPT_NAME')));
2872  $SFN_A = explode('/', strrev($SFN));
2873  $acc = [];
2874  foreach ($SN_A as $kk => $vv) {
2875  if ((string)$SFN_A[$kk] === (string)$vv) {
2876  $acc[] = $vv;
2877  } else {
2878  break;
2879  }
2880  }
2881  $commonEnd = strrev(implode('/', $acc));
2882  if ((string)$commonEnd !== '') {
2883  $retVal = substr($SFN, 0, -(strlen($commonEnd) + 1));
2884  }
2885  break;
2886  case 'TYPO3_HOST_ONLY':
2887  $httpHost = self::getIndpEnv('HTTP_HOST');
2888  $httpHostBracketPosition = strpos($httpHost, ']');
2889  $httpHostParts = explode(':', $httpHost);
2890  $retVal = $httpHostBracketPosition !== false ? substr($httpHost, 0, $httpHostBracketPosition + 1) : array_shift($httpHostParts);
2891  break;
2892  case 'TYPO3_PORT':
2893  $httpHost = self::getIndpEnv('HTTP_HOST');
2894  $httpHostOnly = self::getIndpEnv('TYPO3_HOST_ONLY');
2895  $retVal = strlen($httpHost) > strlen($httpHostOnly) ? substr($httpHost, strlen($httpHostOnly) + 1) : '';
2896  break;
2897  case 'TYPO3_REQUEST_HOST':
2898  $retVal = (self::getIndpEnv('TYPO3_SSL') ? 'https://' : 'http://') . self::getIndpEnv('HTTP_HOST');
2899  break;
2900  case 'TYPO3_REQUEST_URL':
2901  $retVal = self::getIndpEnv('TYPO3_REQUEST_HOST') . self::getIndpEnv('REQUEST_URI');
2902  break;
2903  case 'TYPO3_REQUEST_SCRIPT':
2904  $retVal = self::getIndpEnv('TYPO3_REQUEST_HOST') . self::getIndpEnv('SCRIPT_NAME');
2905  break;
2906  case 'TYPO3_REQUEST_DIR':
2907  $retVal = self::getIndpEnv('TYPO3_REQUEST_HOST') . self::dirname(self::getIndpEnv('SCRIPT_NAME')) . '/';
2908  break;
2909  case 'TYPO3_SITE_URL':
2910  $url = self::getIndpEnv('TYPO3_REQUEST_DIR');
2911  // This can only be set by external entry scripts
2912  if (defined('TYPO3_PATH_WEB')) {
2913  $retVal = $url;
2914  } elseif (‪Environment::getCurrentScript()) {
2916  $siteUrl = substr($url, 0, -strlen($lPath));
2917  if (substr($siteUrl, -1) !== '/') {
2918  $siteUrl .= '/';
2919  }
2920  $retVal = $siteUrl;
2921  }
2922  break;
2923  case 'TYPO3_SITE_PATH':
2924  $retVal = substr(self::getIndpEnv('TYPO3_SITE_URL'), strlen(self::getIndpEnv('TYPO3_REQUEST_HOST')));
2925  break;
2926  case 'TYPO3_SITE_SCRIPT':
2927  $retVal = substr(self::getIndpEnv('TYPO3_REQUEST_URL'), strlen(self::getIndpEnv('TYPO3_SITE_URL')));
2928  break;
2929  case 'TYPO3_SSL':
2930  $proxySSL = trim(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxySSL'] ?? null);
2931  if ($proxySSL === '*') {
2932  $proxySSL = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyIP'];
2933  }
2934  if (self::cmpIP($_SERVER['REMOTE_ADDR'] ?? '', $proxySSL)) {
2935  $retVal = true;
2936  } else {
2937  // https://secure.php.net/manual/en/reserved.variables.server.php
2938  // "Set to a non-empty value if the script was queried through the HTTPS protocol."
2939  $retVal = !empty($_SERVER['SSL_SESSION_ID'])
2940  || (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off');
2941  }
2942  break;
2943  case '_ARRAY':
2944  $out = [];
2945  // Here, list ALL possible keys to this function for debug display.
2946  $envTestVars = [
2947  'HTTP_HOST',
2948  'TYPO3_HOST_ONLY',
2949  'TYPO3_PORT',
2950  'PATH_INFO',
2951  'QUERY_STRING',
2952  'REQUEST_URI',
2953  'HTTP_REFERER',
2954  'TYPO3_REQUEST_HOST',
2955  'TYPO3_REQUEST_URL',
2956  'TYPO3_REQUEST_SCRIPT',
2957  'TYPO3_REQUEST_DIR',
2958  'TYPO3_SITE_URL',
2959  'TYPO3_SITE_SCRIPT',
2960  'TYPO3_SSL',
2961  'TYPO3_REV_PROXY',
2962  'SCRIPT_NAME',
2963  'TYPO3_DOCUMENT_ROOT',
2964  'SCRIPT_FILENAME',
2965  'REMOTE_ADDR',
2966  'REMOTE_HOST',
2967  'HTTP_USER_AGENT',
2968  'HTTP_ACCEPT_LANGUAGE'
2969  ];
2970  foreach ($envTestVars as $v) {
2971  $out[$v] = self::getIndpEnv($v);
2972  }
2973  reset($out);
2974  $retVal = $out;
2975  break;
2976  }
2977  self::$indpEnvCache[$getEnvName] = $retVal;
2978  return $retVal;
2979  }
2980 
2989  public static function isAllowedHostHeaderValue($hostHeaderValue)
2990  {
2991  if (static::$allowHostHeaderValue === true) {
2992  return true;
2993  }
2994 
2995  if (static::isInternalRequestType()) {
2996  return static::$allowHostHeaderValue = true;
2997  }
2998 
2999  // Deny the value if trusted host patterns is empty, which means we are early in the bootstrap
3000  if (empty(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'])) {
3001  return false;
3002  }
3003 
3004  if (‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] === self::ENV_TRUSTED_HOSTS_PATTERN_ALLOW_ALL) {
3005  static::$allowHostHeaderValue = true;
3006  } else {
3007  static::$allowHostHeaderValue = static::hostHeaderValueMatchesTrustedHostsPattern($hostHeaderValue);
3008  }
3009 
3010  return static::$allowHostHeaderValue;
3011  }
3012 
3020  public static function hostHeaderValueMatchesTrustedHostsPattern($hostHeaderValue)
3021  {
3022  if (‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] === self::ENV_TRUSTED_HOSTS_PATTERN_SERVER_NAME) {
3023  // Allow values that equal the server name
3024  // Note that this is only secure if name base virtual host are configured correctly in the webserver
3025  $defaultPort = self::getIndpEnv('TYPO3_SSL') ? '443' : '80';
3026  $parsedHostValue = parse_url('http://' . $hostHeaderValue);
3027  if (isset($parsedHostValue['port'])) {
3028  $hostMatch = (strtolower($parsedHostValue['host']) === strtolower($_SERVER['SERVER_NAME']) && (string)$parsedHostValue['port'] === $_SERVER['SERVER_PORT']);
3029  } else {
3030  $hostMatch = (strtolower($hostHeaderValue) === strtolower($_SERVER['SERVER_NAME']) && $defaultPort === $_SERVER['SERVER_PORT']);
3031  }
3032  } else {
3033  // In case name based virtual hosts are not possible, we allow setting a trusted host pattern
3034  // See https://typo3.org/teams/security/security-bulletins/typo3-core/typo3-core-sa-2014-001/ for further details
3035  $hostMatch = (bool)preg_match('/^' . ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] . '$/i', $hostHeaderValue);
3036  }
3037 
3038  return $hostMatch;
3039  }
3040 
3051  protected static function isInternalRequestType()
3052  {
3053  return ‪Environment::isCli() || !defined('TYPO3_REQUESTTYPE') || (defined('TYPO3_REQUESTTYPE') && TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_INSTALL);
3054  }
3055 
3061  public static function milliseconds()
3062  {
3063  return round(microtime(true) * 1000);
3064  }
3065 
3073  public static function clientInfo($useragent = '')
3074  {
3075  trigger_error('GeneralUtility::clientInfo() will be removed in TYPO3 v10.0. Use your own detection via HTTP_USER_AGENT Server string.', E_USER_DEPRECATED);
3076  if (!$useragent) {
3077  $useragent = self::getIndpEnv('HTTP_USER_AGENT');
3078  }
3079  $bInfo = [];
3080  // Which browser?
3081  if (strpos($useragent, 'Konqueror') !== false) {
3082  $bInfo['BROWSER'] = 'konqu';
3083  } elseif (strpos($useragent, 'Opera') !== false) {
3084  $bInfo['BROWSER'] = 'opera';
3085  } elseif (strpos($useragent, 'MSIE') !== false) {
3086  $bInfo['BROWSER'] = 'msie';
3087  } elseif (strpos($useragent, 'Mozilla') !== false) {
3088  $bInfo['BROWSER'] = 'net';
3089  } elseif (strpos($useragent, 'Flash') !== false) {
3090  $bInfo['BROWSER'] = 'flash';
3091  }
3092  if (isset($bInfo['BROWSER'])) {
3093  // Browser version
3094  switch ($bInfo['BROWSER']) {
3095  case 'net':
3096  $bInfo['VERSION'] = (float)substr($useragent, 8);
3097  if (strpos($useragent, 'Netscape6/') !== false) {
3098  $bInfo['VERSION'] = (float)substr(strstr($useragent, 'Netscape6/'), 10);
3099  }
3100  // Will we ever know if this was a typo or intention...?! :-(
3101  if (strpos($useragent, 'Netscape/6') !== false) {
3102  $bInfo['VERSION'] = (float)substr(strstr($useragent, 'Netscape/6'), 10);
3103  }
3104  if (strpos($useragent, 'Netscape/7') !== false) {
3105  $bInfo['VERSION'] = (float)substr(strstr($useragent, 'Netscape/7'), 9);
3106  }
3107  break;
3108  case 'msie':
3109  $tmp = strstr($useragent, 'MSIE');
3110  $bInfo['VERSION'] = (float)preg_replace('/^[^0-9]*/', '', substr($tmp, 4));
3111  break;
3112  case 'opera':
3113  $tmp = strstr($useragent, 'Opera');
3114  $bInfo['VERSION'] = (float)preg_replace('/^[^0-9]*/', '', substr($tmp, 5));
3115  break;
3116  case 'konqu':
3117  $tmp = strstr($useragent, 'Konqueror/');
3118  $bInfo['VERSION'] = (float)substr($tmp, 10);
3119  break;
3120  }
3121  // Client system
3122  if (strpos($useragent, 'Win') !== false) {
3123  $bInfo['SYSTEM'] = 'win';
3124  } elseif (strpos($useragent, 'Mac') !== false) {
3125  $bInfo['SYSTEM'] = 'mac';
3126  } elseif (strpos($useragent, 'Linux') !== false || strpos($useragent, 'X11') !== false || strpos($useragent, 'SGI') !== false || strpos($useragent, ' SunOS ') !== false || strpos($useragent, ' HP-UX ') !== false) {
3127  $bInfo['SYSTEM'] = 'unix';
3128  }
3129  }
3130  return $bInfo;
3131  }
3132 
3140  public static function getHostname($requestHost = true)
3141  {
3142  trigger_error('GeneralUtility::getHostname() should not be used any longer, this method will be removed in TYPO3 v10.0.', E_USER_DEPRECATED);
3143  $host = '';
3144  // If not called from the command-line, resolve on getIndpEnv()
3145  if ($requestHost && !‪Environment::isCli()) {
3146  $host = self::getIndpEnv('HTTP_HOST');
3147  }
3148  if (!$host) {
3149  // will fail for PHP 4.1 and 4.2
3150  $host = @php_uname('n');
3151  // 'n' is ignored in broken installations
3152  if (strpos($host, ' ')) {
3153  $host = '';
3154  }
3155  }
3156  // We have not found a FQDN yet
3157  if ($host && strpos($host, '.') === false) {
3158  $ip = gethostbyname($host);
3159  // We got an IP address
3160  if ($ip != $host) {
3161  $fqdn = gethostbyaddr($ip);
3162  if ($ip != $fqdn) {
3163  $host = $fqdn;
3164  }
3165  }
3166  }
3167  if (!$host) {
3168  $host = 'localhost.localdomain';
3169  }
3170  return $host;
3171  }
3172 
3173  /*************************
3174  *
3175  * TYPO3 SPECIFIC FUNCTIONS
3176  *
3177  *************************/
3187  public static function getFileAbsFileName($filename)
3188  {
3189  if ((string)$filename === '') {
3190  return '';
3191  }
3192  // Extension
3193  if (strpos($filename, 'EXT:') === 0) {
3194  list($extKey, $local) = explode('/', substr($filename, 4), 2);
3195  $filename = '';
3196  if ((string)$extKey !== '' && ‪ExtensionManagementUtility::isLoaded($extKey) && (string)$local !== '') {
3197  $filename = ‪ExtensionManagementUtility::extPath($extKey) . $local;
3198  }
3199  } elseif (!static::isAbsPath($filename)) {
3200  // is relative. Prepended with the public web folder
3201  $filename = ‪Environment::getPublicPath() . '/' . $filename;
3202  } elseif (!(
3203  static::isFirstPartOfStr($filename, ‪Environment::getProjectPath())
3204  || static::isFirstPartOfStr($filename, ‪Environment::getPublicPath())
3205  )) {
3206  // absolute, but set to blank if not allowed
3207  $filename = '';
3208  }
3209  if ((string)$filename !== '' && static::validPathStr($filename)) {
3210  // checks backpath.
3211  return $filename;
3212  }
3213  return '';
3214  }
3215 
3227  public static function validPathStr($theFile)
3228  {
3229  return strpos($theFile, '//') === false && strpos($theFile, '\\') === false
3230  && preg_match('#(?:^\\.\\.|/\\.\\./|[[:cntrl:]])#u', $theFile) === 0;
3231  }
3232 
3239  public static function isAbsPath($path)
3240  {
3241  if (substr($path, 0, 6) === 'vfs://') {
3242  return true;
3243  }
3244  return isset($path[0]) && $path[0] === '/' || ‪Environment::isWindows() && (strpos($path, ':/') === 1 || strpos($path, ':\\') === 1);
3245  }
3246 
3253  public static function isAllowedAbsPath($path)
3254  {
3255  if (substr($path, 0, 6) === 'vfs://') {
3256  return true;
3257  }
3258  $lockRootPath = ‪$GLOBALS['TYPO3_CONF_VARS']['BE']['lockRootPath'];
3259  return static::isAbsPath($path) && static::validPathStr($path)
3260  && (
3261  static::isFirstPartOfStr($path, ‪Environment::getProjectPath())
3262  || static::isFirstPartOfStr($path, ‪Environment::getPublicPath())
3263  || $lockRootPath && static::isFirstPartOfStr($path, $lockRootPath)
3264  );
3265  }
3266 
3276  public static function verifyFilenameAgainstDenyPattern($filename)
3277  {
3278  $pattern = '/[[:cntrl:]]/';
3279  if ((string)$filename !== '' && (string)‪$GLOBALS['TYPO3_CONF_VARS']['BE']['fileDenyPattern'] !== '') {
3280  $pattern = '/(?:[[:cntrl:]]|' . ‪$GLOBALS['TYPO3_CONF_VARS']['BE']['fileDenyPattern'] . ')/iu';
3281  }
3282  return preg_match($pattern, $filename) === 0;
3283  }
3284 
3291  public static function copyDirectory($source, $destination)
3292  {
3293  if (strpos($source, ‪Environment::getProjectPath() . '/') === false) {
3294  $source = ‪Environment::getPublicPath() . '/' . $source;
3295  }
3296  if (strpos($destination, ‪Environment::getProjectPath() . '/') === false) {
3297  $destination = ‪Environment::getPublicPath() . '/' . $destination;
3298  }
3299  if (static::isAllowedAbsPath($source) && static::isAllowedAbsPath($destination)) {
3300  static::mkdir_deep($destination);
3301  $iterator = new \RecursiveIteratorIterator(
3302  new \RecursiveDirectoryIterator($source, \RecursiveDirectoryIterator::SKIP_DOTS),
3303  \RecursiveIteratorIterator::SELF_FIRST
3304  );
3305  foreach ($iterator as $item) {
3306  $target = $destination . '/' . $iterator->getSubPathName();
3307  if ($item->isDir()) {
3308  static::mkdir($target);
3309  } else {
3310  static::upload_copy_move($item, $target);
3311  }
3312  }
3313  }
3314  }
3315 
3326  public static function sanitizeLocalUrl($url = '')
3327  {
3328  $sanitizedUrl = '';
3329  if (!empty($url)) {
3330  $decodedUrl = rawurldecode($url);
3331  $parsedUrl = parse_url($decodedUrl);
3332  $testAbsoluteUrl = self::resolveBackPath($decodedUrl);
3333  $testRelativeUrl = self::resolveBackPath(self::dirname(self::getIndpEnv('SCRIPT_NAME')) . '/' . $decodedUrl);
3334  // Pass if URL is on the current host:
3335  if (self::isValidUrl($decodedUrl)) {
3336  if (self::isOnCurrentHost($decodedUrl) && strpos($decodedUrl, self::getIndpEnv('TYPO3_SITE_URL')) === 0) {
3337  $sanitizedUrl = $url;
3338  }
3339  } elseif (self::isAbsPath($decodedUrl) && self::isAllowedAbsPath($decodedUrl)) {
3340  $sanitizedUrl = $url;
3341  } elseif (strpos($testAbsoluteUrl, self::getIndpEnv('TYPO3_SITE_PATH')) === 0 && $decodedUrl[0] === '/' &&
3342  substr($decodedUrl, 0, 2) !== '//'
3343  ) {
3344  $sanitizedUrl = $url;
3345  } elseif (empty($parsedUrl['scheme']) && strpos($testRelativeUrl, self::getIndpEnv('TYPO3_SITE_PATH')) === 0
3346  && $decodedUrl[0] !== '/' && strpbrk($decodedUrl, '*:|"<>') === false && strpos($decodedUrl, '\\\\') === false
3347  ) {
3348  $sanitizedUrl = $url;
3349  }
3350  }
3351  if (!empty($url) && empty($sanitizedUrl)) {
3352  static::getLogger()->notice('The URL "' . $url . '" is not considered to be local and was denied.');
3353  }
3354  return $sanitizedUrl;
3355  }
3356 
3365  public static function upload_copy_move($source, $destination)
3366  {
3367  if (is_array(‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\CMS\Core\Utility\GeneralUtility']['moveUploadedFile'] ?? null)) {
3368  $params = ['source' => $source, 'destination' => $destination, 'method' => 'upload_copy_move'];
3369  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\CMS\Core\Utility\GeneralUtility']['moveUploadedFile'] as $hookMethod) {
3370  $fakeThis = false;
3371  self::callUserFunction($hookMethod, $params, $fakeThis);
3372  }
3373  }
3374 
3375  $result = false;
3376  if (is_uploaded_file($source)) {
3377  // Return the value of move_uploaded_file, and if FALSE the temporary $source is still
3378  // around so the user can use unlink to delete it:
3379  $result = move_uploaded_file($source, $destination);
3380  } else {
3381  @copy($source, $destination);
3382  }
3383  // Change the permissions of the file
3384  self::fixPermissions($destination);
3385  // If here the file is copied and the temporary $source is still around,
3386  // so when returning FALSE the user can try unlink to delete the $source
3387  return $result;
3388  }
3389 
3399  public static function upload_to_tempfile($uploadedFileName)
3400  {
3401  if (is_uploaded_file($uploadedFileName)) {
3402  $tempFile = self::tempnam('upload_temp_');
3403  if (is_array(‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\CMS\Core\Utility\GeneralUtility']['moveUploadedFile'] ?? null)) {
3404  $params = ['source' => $uploadedFileName, 'destination' => $tempFile, 'method' => 'upload_to_tempfile'];
3405  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\CMS\Core\Utility\GeneralUtility']['moveUploadedFile'] as $hookMethod) {
3406  $fakeThis = false;
3407  self::callUserFunction($hookMethod, $params, $fakeThis);
3408  }
3409  }
3410 
3411  move_uploaded_file($uploadedFileName, $tempFile);
3412  return @is_file($tempFile) ? $tempFile : '';
3413  }
3414  }
3415 
3425  public static function unlink_tempfile($uploadedTempFileName)
3426  {
3427  if ($uploadedTempFileName) {
3428  $uploadedTempFileName = self::fixWindowsFilePath($uploadedTempFileName);
3429  if (
3430  self::validPathStr($uploadedTempFileName)
3431  && (
3432  self::isFirstPartOfStr($uploadedTempFileName, ‪Environment::getPublicPath() . '/typo3temp/')
3433  || self::isFirstPartOfStr($uploadedTempFileName, ‪Environment::getVarPath() . '/')
3434  )
3435  && @is_file($uploadedTempFileName)
3436  ) {
3437  if (unlink($uploadedTempFileName)) {
3438  return true;
3439  }
3440  }
3441  }
3442  }
3443 
3454  public static function tempnam($filePrefix, $fileSuffix = '')
3455  {
3456  $temporaryPath = ‪Environment::getVarPath() . '/transient/';
3457  if (!is_dir($temporaryPath)) {
3458  self::mkdir_deep($temporaryPath);
3459  }
3460  if ($fileSuffix === '') {
3461  $tempFileName = $temporaryPath . ‪PathUtility::basename(tempnam($temporaryPath, $filePrefix));
3462  } else {
3463  do {
3464  $tempFileName = $temporaryPath . $filePrefix . mt_rand(1, PHP_INT_MAX) . $fileSuffix;
3465  } while (file_exists($tempFileName));
3466  touch($tempFileName);
3467  clearstatcache(null, $tempFileName);
3468  }
3469  return $tempFileName;
3470  }
3471 
3480  public static function stdAuthCode($uid_or_record, ‪$fields = '', $codeLength = 8)
3481  {
3482  if (is_array($uid_or_record)) {
3483  $recCopy_temp = [];
3484  if (‪$fields) {
3485  $fieldArr = self::trimExplode(',', ‪$fields, true);
3486  foreach ($fieldArr as $k => $v) {
3487  $recCopy_temp[$k] = $uid_or_record[$v];
3488  }
3489  } else {
3490  $recCopy_temp = $uid_or_record;
3491  }
3492  $preKey = implode('|', $recCopy_temp);
3493  } else {
3494  $preKey = $uid_or_record;
3495  }
3496  $authCode = $preKey . '||' . ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'];
3497  $authCode = substr(md5($authCode), 0, $codeLength);
3498  return $authCode;
3499  }
3500 
3507  public static function hideIfNotTranslated($l18n_cfg_fieldValue)
3508  {
3509  return ‪$GLOBALS['TYPO3_CONF_VARS']['FE']['hidePagesIfNotTranslatedByDefault'] xor ($l18n_cfg_fieldValue & 2);
3510  }
3511 
3519  public static function hideIfDefaultLanguage($localizationConfiguration)
3520  {
3521  return (bool)($localizationConfiguration & 1);
3522  }
3523 
3533  public static function llXmlAutoFileName($fileRef, $language, $sameLocation = false)
3534  {
3535  trigger_error('This method will be removed in TYPO3 v10.0, the functionality has been moved into AbstractXmlParser.', E_USER_DEPRECATED);
3536  // If $fileRef is already prefixed with "[language key]" then we should return it as is
3537  $fileName = ‪PathUtility::basename($fileRef);
3538  if (self::isFirstPartOfStr($fileName, $language . '.')) {
3539  return $fileRef;
3540  }
3541 
3542  if ($sameLocation) {
3543  return str_replace($fileName, $language . '.' . $fileName, $fileRef);
3544  }
3545 
3546  // Analyze file reference
3547  if (self::isFirstPartOfStr($fileRef, ‪Environment::getFrameworkBasePath() . '/')) {
3548  // Is system
3549  $validatedPrefix = ‪Environment::getFrameworkBasePath() . '/';
3550  } elseif (self::isFirstPartOfStr($fileRef, ‪Environment::getBackendPath() . '/ext/')) {
3551  // Is global
3552  $validatedPrefix = ‪Environment::getBackendPath() . '/ext/';
3553  } elseif (self::isFirstPartOfStr($fileRef, ‪Environment::getExtensionsPath() . '/')) {
3554  // Is local
3555  $validatedPrefix = ‪Environment::getExtensionsPath() . '/';
3556  } else {
3557  $validatedPrefix = '';
3558  }
3559  if ($validatedPrefix) {
3560  // Divide file reference into extension key, directory (if any) and base name:
3561  list($file_extKey, $file_extPath) = explode('/', substr($fileRef, strlen($validatedPrefix)), 2);
3562  $temp = self::revExplode('/', $file_extPath, 2);
3563  if (count($temp) === 1) {
3564  array_unshift($temp, '');
3565  }
3566  // Add empty first-entry if not there.
3567  list($file_extPath, $file_fileName) = $temp;
3568  // The filename is prefixed with "[language key]." because it prevents the llxmltranslate tool from detecting it.
3569  $location = 'typo3conf/l10n/' . $language . '/' . $file_extKey . '/' . ($file_extPath ? $file_extPath . '/' : '');
3570  return $location . $language . '.' . $file_fileName;
3571  }
3572  return null;
3573  }
3574 
3585  public static function callUserFunction($funcName, &$params, &$ref)
3586  {
3587  // Check if we're using a closure and invoke it directly.
3588  if (is_object($funcName) && is_a($funcName, 'Closure')) {
3589  return call_user_func_array($funcName, [&$params, &$ref]);
3590  }
3591  $funcName = trim($funcName);
3592  $parts = explode('->', $funcName);
3593  // Call function or method
3594  if (count($parts) === 2) {
3595  // It's a class/method
3596  // Check if class/method exists:
3597  if (class_exists($parts[0])) {
3598  // Create object
3599  $classObj = self::makeInstance($parts[0]);
3600  if (method_exists($classObj, $parts[1])) {
3601  // Call method:
3602  $content = call_user_func_array([&$classObj, $parts[1]], [&$params, &$ref]);
3603  } else {
3604  $errorMsg = 'No method name \'' . $parts[1] . '\' in class ' . $parts[0];
3605  throw new \InvalidArgumentException($errorMsg, 1294585865);
3606  }
3607  } else {
3608  $errorMsg = 'No class named ' . $parts[0];
3609  throw new \InvalidArgumentException($errorMsg, 1294585866);
3610  }
3611  } elseif (function_exists($funcName)) {
3612  // It's a function
3613  $content = call_user_func_array($funcName, [&$params, &$ref]);
3614  } else {
3615  $errorMsg = 'No function named: ' . $funcName;
3616  throw new \InvalidArgumentException($errorMsg, 1294585867);
3617  }
3618  return $content;
3619  }
3620 
3633  public static function getUserObj($className)
3634  {
3635  trigger_error('This method will be removed in TYPO3 v10.0, use GeneralUtility::makeInstance() directly instead.', E_USER_DEPRECATED);
3636  // Check if class exists:
3637  if (class_exists($className)) {
3638  return self::makeInstance($className);
3639  }
3640  }
3641 
3662  public static function makeInstance($className, ...$constructorArguments)
3663  {
3664  if (!is_string($className) || empty($className)) {
3665  throw new \InvalidArgumentException('$className must be a non empty string.', 1288965219);
3666  }
3667  // Never instantiate with a beginning backslash, otherwise things like singletons won't work.
3668  if ($className[0] === '\\') {
3669  throw new \InvalidArgumentException(
3670  '$className "' . $className . '" must not start with a backslash.',
3671  1420281366
3672  );
3673  }
3674  if (isset(static::$finalClassNameCache[$className])) {
3675  $finalClassName = static::$finalClassNameCache[$className];
3676  } else {
3677  $finalClassName = self::getClassName($className);
3678  static::$finalClassNameCache[$className] = $finalClassName;
3679  }
3680  // Return singleton instance if it is already registered
3681  if (isset(self::$singletonInstances[$finalClassName])) {
3682  return self::$singletonInstances[$finalClassName];
3683  }
3684  // Return instance if it has been injected by addInstance()
3685  if (
3686  isset(self::$nonSingletonInstances[$finalClassName])
3687  && !empty(self::$nonSingletonInstances[$finalClassName])
3688  ) {
3689  return array_shift(self::$nonSingletonInstances[$finalClassName]);
3690  }
3691  // Create new instance and call constructor with parameters
3692  $instance = new $finalClassName(...$constructorArguments);
3693  // Register new singleton instance
3694  if ($instance instanceof SingletonInterface) {
3695  self::$singletonInstances[$finalClassName] = $instance;
3696  }
3697  if ($instance instanceof LoggerAwareInterface) {
3698  $instance->setLogger(static::makeInstance(LogManager::class)->getLogger($className));
3699  }
3700  return $instance;
3701  }
3702 
3710  protected static function getClassName($className)
3711  {
3712  if (class_exists($className)) {
3713  while (static::classHasImplementation($className)) {
3714  $className = static::getImplementationForClass($className);
3715  }
3716  }
3718  }
3719 
3726  protected static function getImplementationForClass($className)
3727  {
3728  return ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][$className]['className'];
3729  }
3730 
3737  protected static function classHasImplementation($className)
3738  {
3739  return !empty(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][$className]['className']);
3740  }
3741 
3759  public static function setSingletonInstance($className, SingletonInterface $instance)
3760  {
3761  self::checkInstanceClassName($className, $instance);
3762  // Check for XCLASS registration (same is done in makeInstance() in order to store the singleton of the final class name)
3763  $finalClassName = self::getClassName($className);
3764  self::$singletonInstances[$finalClassName] = $instance;
3765  }
3766 
3782  public static function removeSingletonInstance($className, SingletonInterface $instance)
3783  {
3784  self::checkInstanceClassName($className, $instance);
3785  if (!isset(self::$singletonInstances[$className])) {
3786  throw new \InvalidArgumentException('No Instance registered for ' . $className . '.', 1394099179);
3787  }
3788  if ($instance !== self::$singletonInstances[$className]) {
3789  throw new \InvalidArgumentException('The instance you are trying to remove has not been registered before.', 1394099256);
3790  }
3791  unset(self::$singletonInstances[$className]);
3792  }
3793 
3807  public static function resetSingletonInstances(array $newSingletonInstances)
3808  {
3809  static::$singletonInstances = [];
3810  foreach ($newSingletonInstances as $className => $instance) {
3811  static::setSingletonInstance($className, $instance);
3812  }
3813  }
3814 
3827  public static function getSingletonInstances()
3828  {
3829  return static::$singletonInstances;
3830  }
3831 
3843  public static function getInstances()
3844  {
3845  return static::$nonSingletonInstances;
3846  }
3847 
3862  public static function addInstance($className, $instance)
3863  {
3864  self::checkInstanceClassName($className, $instance);
3865  if ($instance instanceof SingletonInterface) {
3866  throw new \InvalidArgumentException('$instance must not be an instance of TYPO3\\CMS\\Core\\SingletonInterface. ' . 'For setting singletons, please use setSingletonInstance.', 1288969325);
3867  }
3868  if (!isset(self::$nonSingletonInstances[$className])) {
3869  self::$nonSingletonInstances[$className] = [];
3870  }
3871  self::$nonSingletonInstances[$className][] = $instance;
3872  }
3873 
3882  protected static function checkInstanceClassName($className, $instance)
3883  {
3884  if ($className === '') {
3885  throw new \InvalidArgumentException('$className must not be empty.', 1288967479);
3886  }
3887  if (!$instance instanceof $className) {
3888  throw new \InvalidArgumentException('$instance must be an instance of ' . $className . ', but actually is an instance of ' . get_class($instance) . '.', 1288967686);
3889  }
3890  }
3891 
3902  public static function purgeInstances()
3903  {
3904  self::$singletonInstances = [];
3905  self::$nonSingletonInstances = [];
3906  }
3907 
3915  public static function flushInternalRuntimeCaches()
3916  {
3917  self::$indpEnvCache = [];
3918  self::$idnaStringCache = [];
3919  }
3920 
3930  public static function makeInstanceService($serviceType, $serviceSubType = '', $excludeServiceKeys = [])
3931  {
3932  $error = false;
3933  if (!is_array($excludeServiceKeys)) {
3934  $excludeServiceKeys = self::trimExplode(',', $excludeServiceKeys, true);
3935  }
3936  $requestInfo = [
3937  'requestedServiceType' => $serviceType,
3938  'requestedServiceSubType' => $serviceSubType,
3939  'requestedExcludeServiceKeys' => $excludeServiceKeys
3940  ];
3941  while ($info = ‪ExtensionManagementUtility::findService($serviceType, $serviceSubType, $excludeServiceKeys)) {
3942  // provide information about requested service to service object
3943  $info = array_merge($info, $requestInfo);
3944  // Check persistent object and if found, call directly and exit.
3945  if (is_object(‪$GLOBALS['T3_VAR']['makeInstanceService'][$info['className']])) {
3946  // update request info in persistent object
3947  ‪$GLOBALS['T3_VAR']['makeInstanceService'][$info['className']]->info = $info;
3948  // reset service and return object
3949  ‪$GLOBALS['T3_VAR']['makeInstanceService'][$info['className']]->reset();
3950  return ‪$GLOBALS['T3_VAR']['makeInstanceService'][$info['className']];
3951  }
3952  $obj = self::makeInstance($info['className']);
3953  if (is_object($obj)) {
3954  if (!@is_callable([$obj, 'init'])) {
3955  // use silent logging??? I don't think so.
3956  die('Broken service:' . ‪DebugUtility::viewArray($info));
3957  }
3958  $obj->info = $info;
3959  // service available?
3960  if ($obj->init()) {
3961  // create persistent object
3962  ‪$GLOBALS['T3_VAR']['makeInstanceService'][$info['className']] = $obj;
3963  return $obj;
3964  }
3965  $error = $obj->getLastErrorArray();
3966  unset($obj);
3967  }
3968 
3969  // deactivate the service
3970  ‪ExtensionManagementUtility::deactivateService($info['serviceType'], $info['serviceKey']);
3971  }
3972  return $error;
3973  }
3974 
3981  public static function initSysLog()
3982  {
3983  // Init custom logging
3984  $params = ['initLog' => true];
3985  $fakeThis = false;
3986  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_div.php']['systemLog'] ?? [] as $hookMethod) {
3987  self::callUserFunction($hookMethod, $params, $fakeThis);
3988  }
3989  ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['systemLogLevel'] = ‪MathUtility::forceIntegerInRange(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['systemLogLevel'], 0, 4);
3990  ‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_div.php']['systemLogInit'] = true;
3991  }
3992 
4005  public static function sysLog($msg, $extKey, $severity = 0)
4006  {
4007  trigger_error('GeneralUtility::sysLog() will be removed with TYPO3 v10.0.', E_USER_DEPRECATED);
4008 
4009  $severity = ‪MathUtility::forceIntegerInRange($severity, 0, 4);
4010  // Is message worth logging?
4011  if ((int)‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['systemLogLevel'] > $severity) {
4012  return;
4013  }
4014  // Initialize logging
4015  if (!‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_div.php']['systemLogInit']) {
4016  self::initSysLog();
4017  }
4018  // Do custom logging; avoid calling debug_backtrace if there are no custom loggers.
4019  if (!empty(‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_div.php']['systemLog'])) {
4020  $params = ['msg' => $msg, 'extKey' => $extKey, 'backTrace' => debug_backtrace(), 'severity' => $severity];
4021  $fakeThis = false;
4022  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_div.php']['systemLog'] as $hookMethod) {
4023  self::callUserFunction($hookMethod, $params, $fakeThis);
4024  }
4025  }
4026  // TYPO3 logging enabled?
4027  if (!‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['systemLog']) {
4028  return;
4029  }
4030 
4031  static::getLogger()->log(‪LogLevel::INFO - $severity, $msg, ['extension' => $extKey]);
4032  }
4033 
4048  public static function devLog($msg, $extKey, $severity = 0, $dataVar = false)
4049  {
4050  trigger_error('GeneralUtility::devLog() will be removed with TYPO3 v10.0.', E_USER_DEPRECATED);
4051  if (is_array(‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_div.php']['devLog'])) {
4052  $params = ['msg' => $msg, 'extKey' => $extKey, 'severity' => $severity, 'dataVar' => $dataVar];
4053  $fakeThis = false;
4054  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_div.php']['devLog'] as $hookMethod) {
4055  self::callUserFunction($hookMethod, $params, $fakeThis);
4056  }
4057  }
4058  }
4059 
4066  public static function deprecationLog($msg)
4067  {
4068  trigger_error('GeneralUtility::deprecationLog() will be removed in TYPO3 v10.0, use "trigger_error("Given reason", E_USER_DEPRECATED);" to log deprecations.', E_USER_DEPRECATED);
4069  trigger_error($msg, E_USER_DEPRECATED);
4070  }
4071 
4097  public static function logDeprecatedViewHelperAttribute(string $property, RenderingContextInterface $renderingContext, string $additionalMessage = '')
4098  {
4099  trigger_error('GeneralUtility::logDeprecatedViewHelperAttribute() will be removed in TYPO3 v10.0.', E_USER_DEPRECATED);
4100  $template = $renderingContext->getTemplatePaths()->resolveTemplateFileForControllerAndActionAndFormat(
4101  $renderingContext->getControllerName(),
4102  $renderingContext->getControllerAction()
4103  );
4104  $template = str_replace(‪Environment::getPublicPath() . '/', '', $template);
4105  $message = [];
4106  $message[] = '[' . $template . ']';
4107  $message[] = 'The property "' . $property . '" has been marked as deprecated.';
4108  $message[] = $additionalMessage;
4109  $message[] = 'Please check also your partial and layout files of this template';
4110  trigger_error(implode(' ', $message), E_USER_DEPRECATED);
4111  }
4112 
4119  public static function getDeprecationLogFileName()
4120  {
4121  trigger_error('GeneralUtility::getDeprecationLogFileName() will be removed in TYPO3 v10.0.', E_USER_DEPRECATED);
4122  return ‪Environment::getVarPath() . '/log/deprecation_' . self::shortMD5(‪Environment::getProjectPath() . ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey']) . '.log';
4123  }
4124 
4130  public static function logDeprecatedFunction()
4131  {
4132  trigger_error('GeneralUtility::logDeprecatedFunction() will be removed in TYPO3 v10.0.', E_USER_DEPRECATED);
4133  $trail = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
4134  if ($trail[1]['type']) {
4135  $function = new \ReflectionMethod($trail[1]['class'], $trail[1]['function']);
4136  } else {
4137  $function = new \ReflectionFunction($trail[1]['function']);
4138  }
4139  $msg = '';
4140  if (preg_match('/@deprecated\\s+(.*)/', $function->getDocComment(), $match)) {
4141  $msg = $match[1];
4142  }
4143  // Write a longer message to the deprecation log: <function> <annotion> - <trace> (<source>)
4144  $logMsg = $trail[1]['class'] . $trail[1]['type'] . $trail[1]['function'];
4145  $logMsg .= '() - ' . $msg . ' - ' . ‪DebugUtility::debugTrail();
4146  $logMsg .= ' (' . ‪PathUtility::stripPathSitePrefix($function->getFileName()) . '#' . $function->getStartLine() . ')';
4147  trigger_error($logMsg, E_USER_DEPRECATED);
4148  }
4149 
4160  public static function arrayToLogString(array $arr, $valueList = [], $valueLength = 20)
4161  {
4162  trigger_error('GeneralUtility::arrayToLogString() will be removed in TYPO3 v10.0. Use CLI-related methods in your code directly.', E_USER_DEPRECATED);
4163  $str = '';
4164  if (!is_array($valueList)) {
4165  $valueList = self::trimExplode(',', $valueList, true);
4166  }
4167  $valListCnt = count($valueList);
4168  foreach ($arr as $key => $value) {
4169  if (!$valListCnt || in_array($key, $valueList)) {
4170  $str .= (string)$key . trim(': ' . self::fixed_lgd_cs(str_replace(LF, '|', (string)$value), $valueLength)) . '; ';
4171  }
4172  }
4173  return $str;
4174  }
4175 
4184  public static function unQuoteFilenames($parameters, $unQuote = false)
4185  {
4186  trigger_error('GeneralUtility::unQuoteFilenames() should not be used any longer, this method will be removed in TYPO3 v10.0.', E_USER_DEPRECATED);
4187  $paramsArr = explode(' ', trim($parameters));
4188  // Whenever a quote character (") is found, $quoteActive is set to the element number inside of $params. A value of -1 means that there are not open quotes at the current position.
4189  $quoteActive = -1;
4190  foreach ($paramsArr as $k => $v) {
4191  if ($quoteActive > -1) {
4192  $paramsArr[$quoteActive] .= ' ' . $v;
4193  unset($paramsArr[$k]);
4194  if (substr($v, -1) === $paramsArr[$quoteActive][0]) {
4195  $quoteActive = -1;
4196  }
4197  } elseif (!trim($v)) {
4198  // Remove empty elements
4199  unset($paramsArr[$k]);
4200  } elseif (preg_match('/^(["\'])/', $v) && substr($v, -1) !== $v[0]) {
4201  $quoteActive = $k;
4202  }
4203  }
4204  if ($unQuote) {
4205  foreach ($paramsArr as $key => &$val) {
4206  $val = preg_replace('/(?:^"|"$)/', '', $val);
4207  $val = preg_replace('/(?:^\'|\'$)/', '', $val);
4208  }
4209  unset($val);
4210  }
4211  // Return reindexed array
4212  return array_values($paramsArr);
4213  }
4214 
4221  public static function quoteJSvalue($value)
4222  {
4223  return strtr(
4224  json_encode((string)$value, JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_TAG),
4225  [
4226  '"' => '\'',
4227  '\\\\' => '\\u005C',
4228  ' ' => '\\u0020',
4229  '!' => '\\u0021',
4230  '\\t' => '\\u0009',
4231  '\\n' => '\\u000A',
4232  '\\r' => '\\u000D'
4233  ]
4234  );
4235  }
4236 
4247  public static function presetApplicationContext(ApplicationContext $applicationContext)
4248  {
4249  if (static::$applicationContext === null) {
4250  static::$applicationContext = $applicationContext;
4251  } else {
4252  throw new \RuntimeException('Trying to override applicationContext which has already been defined!', 1376084316);
4253  }
4254  }
4255 
4264  public static function resetApplicationContext(): void
4265  {
4266  static::$applicationContext = null;
4267  }
4268 
4274  public static function getApplicationContext()
4275  {
4276  return static::$applicationContext;
4277  }
4278 
4283  public static function isRunningOnCgiServerApi()
4284  {
4285  return in_array(PHP_SAPI, self::$supportedCgiServerApis, true);
4286  }
4287 
4291  protected static function getLogger()
4292  {
4293  return static::makeInstance(LogManager::class)->getLogger(__CLASS__);
4294  }
4295 
4300  private static function writeDeprecationLogFileEntry($msg)
4301  {
4302  $date = date(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] . ' ' . ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'] . ': ');
4303  // Write a longer message to the deprecation log
4304  $destination = ‪Environment::getVarPath() . '/log/deprecation_' . self::shortMD5(‪Environment::getProjectPath() . ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey']) . '.log';
4305  $file = @fopen($destination, 'a');
4306  if ($file) {
4307  @fwrite($file, $date . $msg . LF);
4308  @fclose($file);
4309  self::fixPermissions($destination);
4310  }
4311  }
4312 }
‪TYPO3\CMS\Core\Utility\HttpUtility\idn_to_ascii
‪static string bool idn_to_ascii(string $domain)
Definition: HttpUtility.php:193
‪TYPO3\CMS\Core\Utility\MathUtility\canBeInterpretedAsInteger
‪static bool canBeInterpretedAsInteger($var)
Definition: MathUtility.php:73
‪TYPO3\CMS\Core\Core\Environment\getPublicPath
‪static string getPublicPath()
Definition: Environment.php:153
‪TYPO3\CMS\Core\Utility\GeneralUtility\SYSLOG_SEVERITY_FATAL
‪const SYSLOG_SEVERITY_FATAL
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Core\Core\ApplicationContext
Definition: ApplicationContext.php:36
‪TYPO3\CMS\Core\Log\LogLevel\INFO
‪const INFO
Definition: LogLevel.php:85
‪TYPO3\CMS\Core\Utility\PathUtility\stripPathSitePrefix
‪static string stripPathSitePrefix($path)
Definition: PathUtility.php:371
‪TYPO3\CMS\Core\Utility\GeneralUtility\$allowHostHeaderValue
‪static bool $allowHostHeaderValue
Definition: GeneralUtility.php:62
‪TYPO3\CMS\Core\Utility\MathUtility\forceIntegerInRange
‪static int forceIntegerInRange($theInt, $min, $max=2000000000, $defaultValue=0)
Definition: MathUtility.php:31
‪TYPO3\CMS\Core\Utility\PathUtility\dirnameDuringBootstrap
‪static string dirnameDuringBootstrap($path)
Definition: PathUtility.php:275
‪TYPO3\CMS\Core\Core\Environment\isWindows
‪static bool isWindows()
Definition: Environment.php:266
‪TYPO3\CMS\Core\Utility
Definition: ArrayUtility.php:2
‪TYPO3\CMS\Core\Core\ClassLoadingInformation
Definition: ClassLoadingInformation.php:32
‪TYPO3\CMS\Core\Utility\ExtensionManagementUtility\findService
‪static mixed findService($serviceType, $serviceSubType='', $excludeServiceKeys=[])
Definition: ExtensionManagementUtility.php:1112
‪TYPO3\CMS\Core\Utility\GeneralUtility\$labelArr
‪if($base !==1000 && $base !==1024) $labelArr
Definition: GeneralUtility.php:838
‪$parser
‪$parser
Definition: annotationChecker.php:100
‪TYPO3\CMS\Core\Core\Environment\getCurrentScript
‪static string getCurrentScript()
Definition: Environment.php:193
‪TYPO3\CMS\Core\Utility\GeneralUtility\$multiplier
‪$multiplier
Definition: GeneralUtility.php:851
‪TYPO3\CMS\Core\Utility\ArrayUtility\mergeRecursiveWithOverrule
‪static mergeRecursiveWithOverrule(array &$original, array $overrule, $addKeys=true, $includeEmptyValues=true, $enableUnsetFeature=true)
Definition: ArrayUtility.php:614
‪$dir
‪$dir
Definition: validateRstFiles.php:213
‪TYPO3\CMS\Core\Utility\GeneralUtility\SYSLOG_SEVERITY_ERROR
‪const SYSLOG_SEVERITY_ERROR
Definition: GeneralUtility.php:51
‪$fields
‪$fields
Definition: pages.php:4
‪TYPO3\CMS\Core\Utility\GeneralUtility\$oldLocale
‪$oldLocale
Definition: GeneralUtility.php:841
‪TYPO3\CMS\Core\Utility\PathUtility\basename
‪static string basename($path)
Definition: PathUtility.php:164
‪TYPO3\CMS\Core\Core\Environment\getFrameworkBasePath
‪static string getFrameworkBasePath()
Definition: Environment.php:234
‪TYPO3\CMS\Core\Core\ClassLoadingInformation\getClassNameForAlias
‪static mixed getClassNameForAlias($alias)
Definition: ClassLoadingInformation.php:180
‪TYPO3\CMS\Core\Utility\GeneralUtility\$sizeInUnits
‪$sizeInUnits
Definition: GeneralUtility.php:852
‪TYPO3\CMS\Core\Utility\HttpUtility\buildUrl
‪static string buildUrl(array $urlParts)
Definition: HttpUtility.php:138
‪TYPO3\CMS\Core\Core\Environment\getProjectPath
‪static string getProjectPath()
Definition: Environment.php:142
‪TYPO3\CMS\Core\Utility\GeneralUtility\ENV_TRUSTED_HOSTS_PATTERN_ALLOW_ALL
‪const ENV_TRUSTED_HOSTS_PATTERN_ALLOW_ALL
Definition: GeneralUtility.php:54
‪TYPO3\CMS\Core\Utility\ExtensionManagementUtility\deactivateService
‪static deactivateService($serviceType, $serviceKey)
Definition: ExtensionManagementUtility.php:1212
‪TYPO3\CMS\Core\Utility\DebugUtility\debugTrail
‪static string debugTrail($prependFileNames=false)
Definition: DebugUtility.php:139
‪TYPO3\CMS\Core\Utility\GeneralUtility\$localeInfo
‪if($newLocale) $localeInfo
Definition: GeneralUtility.php:846
‪TYPO3\CMS\Core\Utility\GeneralUtility\$newLocale
‪$newLocale
Definition: GeneralUtility.php:842
‪TYPO3\CMS\Core\Utility\GeneralUtility\SYSLOG_SEVERITY_INFO
‪const SYSLOG_SEVERITY_INFO
Definition: GeneralUtility.php:48
‪TYPO3\CMS\Core\Utility\DebugUtility\viewArray
‪static string viewArray($array_in)
Definition: DebugUtility.php:195
‪TYPO3\CMS\Core\Utility\ExtensionManagementUtility\isLoaded
‪static bool isLoaded($key, $exitOnError=null)
Definition: ExtensionManagementUtility.php:115
‪TYPO3\CMS\Core\Cache\CacheManager
Definition: CacheManager.php:34
‪TYPO3\CMS\Core\Utility\GeneralUtility\SYSLOG_SEVERITY_WARNING
‪const SYSLOG_SEVERITY_WARNING
Definition: GeneralUtility.php:50
‪TYPO3\CMS\Core\Core\Environment\getBackendPath
‪static string getBackendPath()
Definition: Environment.php:223
‪TYPO3\CMS\Core\Service\OpcodeCacheService
Definition: OpcodeCacheService.php:24
‪TYPO3\CMS\Core\Http\RequestFactory
Definition: RequestFactory.php:27
‪TYPO3\CMS\Core\Utility\GeneralUtility\ENV_TRUSTED_HOSTS_PATTERN_SERVER_NAME
‪const ENV_TRUSTED_HOSTS_PATTERN_SERVER_NAME
Definition: GeneralUtility.php:55
‪$output
‪$output
Definition: annotationChecker.php:113
‪TYPO3\CMS\Core\Utility\GeneralUtility\SYSLOG_SEVERITY_NOTICE
‪const SYSLOG_SEVERITY_NOTICE
Definition: GeneralUtility.php:49
‪TYPO3\CMS\Core\Utility\StringUtility\getUniqueId
‪static string getUniqueId($prefix='')
Definition: StringUtility.php:91
‪TYPO3\CMS\Core\SingletonInterface
Definition: SingletonInterface.php:22
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:5
‪TYPO3\CMS\Core\Log\LogManager
Definition: LogManager.php:25
‪TYPO3\CMS\Core\Core\Environment
Definition: Environment.php:39
‪TYPO3\CMS\Core\Utility\GeneralUtility\$sizeInBytes
‪if($newLocale) $sizeInBytes
Definition: GeneralUtility.php:850
‪TYPO3\CMS\Core\Utility\ExtensionManagementUtility\extPath
‪static string extPath($key, $script='')
Definition: ExtensionManagementUtility.php:149
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:45
‪TYPO3\CMS\Core\Core\Environment\isCli
‪static bool isCli()
Definition: Environment.php:127
‪TYPO3\CMS\Core\Log\LogLevel
Definition: LogLevel.php:21
‪TYPO3\CMS\Core\Core\Environment\getVarPath
‪static string getVarPath()
Definition: Environment.php:165
‪TYPO3\CMS\Core\Core\Environment\getExtensionsPath
‪static string getExtensionsPath()
Definition: Environment.php:245