‪TYPO3CMS  10.4
GeneralUtility.php
Go to the documentation of this file.
1 <?php
2 
3 /*
4  * This file is part of the TYPO3 CMS project.
5  *
6  * It is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU General Public License, either version 2
8  * of the License, or any later version.
9  *
10  * For the full copyright and license information, please read the
11  * LICENSE.txt file that was distributed with this source code.
12  *
13  * The TYPO3 project - inspiring people to share!
14  */
15 
17 
18 use Egulias\EmailValidator\EmailValidator;
19 use Egulias\EmailValidator\Validation\RFCValidation;
20 use GuzzleHttp\Exception\RequestException;
21 use Psr\Container\ContainerInterface;
22 use Psr\Log\LoggerAwareInterface;
23 use Psr\Log\LoggerInterface;
33 
46 {
49 
56  protected static ‪$allowHostHeaderValue = false;
57 
61  protected static ‪$container;
62 
69  protected static $singletonInstances = [];
70 
76  protected static $nonSingletonInstances = [];
77 
83  protected static $finalClassNameCache = [];
84 
91  protected static $applicationContext;
92 
96  protected static $indpEnvCache = [];
97 
98  final private function __construct()
99  {
100  }
101 
102  /*************************
103  *
104  * GET/POST Variables
105  *
106  * Background:
107  * Input GET/POST variables in PHP may have their quotes escaped with "\" or not depending on configuration.
108  * TYPO3 has always converted quotes to BE escaped if the configuration told that they would not be so.
109  * But the clean solution is that quotes are never escaped and that is what the functions below offers.
110  * Eventually TYPO3 should provide this in the global space as well.
111  * In the transitional phase (or forever..?) we need to encourage EVERY to read and write GET/POST vars through the API functions below.
112  * This functionality was previously needed to normalize between magic quotes logic, which was removed from PHP 5.4,
113  * so these methods are still in use, but not tackle the slash problem anymore.
114  *
115  *************************/
123  public static function _GP($var)
124  {
125  if (empty($var)) {
126  return;
127  }
128  if (isset($_POST[$var])) {
129  $value = $_POST[$var];
130  } elseif (isset($_GET[$var])) {
131  $value = $_GET[$var];
132  } else {
133  $value = null;
134  }
135  // This is there for backwards-compatibility, in order to avoid NULL
136  if (isset($value) && !is_array($value)) {
137  $value = (string)$value;
138  }
139  return $value;
140  }
141 
148  public static function _GPmerged($parameter)
149  {
150  $postParameter = isset($_POST[$parameter]) && is_array($_POST[$parameter]) ? $_POST[$parameter] : [];
151  $getParameter = isset($_GET[$parameter]) && is_array($_GET[$parameter]) ? $_GET[$parameter] : [];
152  $mergedParameters = $getParameter;
153  ‪ArrayUtility::mergeRecursiveWithOverrule($mergedParameters, $postParameter);
154  return $mergedParameters;
155  }
156 
166  public static function _GET($var = null)
167  {
168  $value = $var === null
169  ? $_GET
170  : (empty($var) ? null : ($_GET[$var] ?? null));
171  // This is there for backwards-compatibility, in order to avoid NULL
172  if (isset($value) && !is_array($value)) {
173  $value = (string)$value;
174  }
175  return $value;
176  }
177 
186  public static function _POST($var = null)
187  {
188  $value = $var === null ? $_POST : (empty($var) || !isset($_POST[$var]) ? null : $_POST[$var]);
189  // This is there for backwards-compatibility, in order to avoid NULL
190  if (isset($value) && !is_array($value)) {
191  $value = (string)$value;
192  }
193  return $value;
194  }
195 
196  /*************************
197  *
198  * STRING FUNCTIONS
199  *
200  *************************/
209  public static function fixed_lgd_cs($string, $chars, $appendString = '...')
210  {
211  if ((int)$chars === 0 || mb_strlen($string, 'utf-8') <= abs($chars)) {
212  return $string;
213  }
214  if ($chars > 0) {
215  $string = mb_substr($string, 0, $chars, 'utf-8') . $appendString;
216  } else {
217  $string = $appendString . mb_substr($string, $chars, mb_strlen($string, 'utf-8'), 'utf-8');
218  }
219  return $string;
220  }
221 
230  public static function cmpIP($baseIP, $list)
231  {
232  $list = trim($list);
233  if ($list === '') {
234  return false;
235  }
236  if ($list === '*') {
237  return true;
238  }
239  if (strpos($baseIP, ':') !== false && self::validIPv6($baseIP)) {
240  return self::cmpIPv6($baseIP, $list);
241  }
242  return self::cmpIPv4($baseIP, $list);
243  }
244 
252  public static function cmpIPv4($baseIP, $list)
253  {
254  $IPpartsReq = explode('.', $baseIP);
255  if (count($IPpartsReq) === 4) {
256  $values = ‪self::trimExplode(',', $list, true);
257  foreach ($values as $test) {
258  $testList = explode('/', $test);
259  if (count($testList) === 2) {
260  [$test, $mask] = $testList;
261  } else {
262  $mask = false;
263  }
264  if ((int)$mask) {
265  $mask = (int)$mask;
266  // "192.168.3.0/24"
267  $lnet = (int)ip2long($test);
268  $lip = (int)ip2long($baseIP);
269  $binnet = str_pad(decbin($lnet), 32, '0', STR_PAD_LEFT);
270  $firstpart = substr($binnet, 0, $mask);
271  $binip = str_pad(decbin($lip), 32, '0', STR_PAD_LEFT);
272  $firstip = substr($binip, 0, $mask);
273  $yes = $firstpart === $firstip;
274  } else {
275  // "192.168.*.*"
276  $IPparts = explode('.', $test);
277  $yes = 1;
278  foreach ($IPparts as $index => $val) {
279  $val = trim($val);
280  if ($val !== '*' && $IPpartsReq[$index] !== $val) {
281  $yes = 0;
282  }
283  }
284  }
285  if ($yes) {
286  return true;
287  }
288  }
289  }
290  return false;
291  }
292 
301  public static function cmpIPv6($baseIP, $list)
302  {
303  // Policy default: Deny connection
304  $success = false;
305  $baseIP = self::normalizeIPv6($baseIP);
306  $values = ‪self::trimExplode(',', $list, true);
307  foreach ($values as $test) {
308  $testList = explode('/', $test);
309  if (count($testList) === 2) {
310  [$test, $mask] = $testList;
311  } else {
312  $mask = false;
313  }
314  if (self::validIPv6($test)) {
315  $test = self::normalizeIPv6($test);
316  $maskInt = (int)$mask ?: 128;
317  // Special case; /0 is an allowed mask - equals a wildcard
318  if ($mask === '0') {
319  $success = true;
320  } elseif ($maskInt == 128) {
321  $success = $test === $baseIP;
322  } else {
323  $testBin = (string)inet_pton($test);
324  $baseIPBin = (string)inet_pton($baseIP);
325 
326  $success = true;
327  // Modulo is 0 if this is a 8-bit-boundary
328  $maskIntModulo = $maskInt % 8;
329  $numFullCharactersUntilBoundary = (int)($maskInt / 8);
330  $substring = (string)substr($baseIPBin, 0, $numFullCharactersUntilBoundary);
331  if (strpos($testBin, $substring) !== 0) {
332  $success = false;
333  } elseif ($maskIntModulo > 0) {
334  // If not an 8-bit-boundary, check bits of last character
335  $testLastBits = str_pad(decbin(ord(substr($testBin, $numFullCharactersUntilBoundary, 1))), 8, '0', STR_PAD_LEFT);
336  $baseIPLastBits = str_pad(decbin(ord(substr($baseIPBin, $numFullCharactersUntilBoundary, 1))), 8, '0', STR_PAD_LEFT);
337  if (strncmp($testLastBits, $baseIPLastBits, $maskIntModulo) != 0) {
338  $success = false;
339  }
340  }
341  }
342  }
343  if ($success) {
344  return true;
345  }
346  }
347  return false;
348  }
349 
357  public static function IPv6Hex2Bin($hex)
358  {
359  trigger_error('GeneralUtility::IPv6Hex2Bin() will be removed in TYPO3 v11.0. Use the native PHP function inet_pton($hex) instead.', E_USER_DEPRECATED);
360  return inet_pton($hex);
361  }
362 
370  public static function IPv6Bin2Hex($bin)
371  {
372  trigger_error('GeneralUtility::IPv6Bin2Hex() will be removed in TYPO3 v11.0. Use the native PHP function inet_ntop($bin) instead.', E_USER_DEPRECATED);
373  return inet_ntop($bin);
374  }
375 
382  public static function normalizeIPv6($address)
383  {
384  $normalizedAddress = '';
385  // According to RFC lowercase-representation is recommended
386  $address = strtolower($address);
387  // Normalized representation has 39 characters (0000:0000:0000:0000:0000:0000:0000:0000)
388  if (strlen($address) === 39) {
389  // Already in full expanded form
390  return $address;
391  }
392  // Count 2 if if address has hidden zero blocks
393  $chunks = explode('::', $address);
394  if (count($chunks) === 2) {
395  $chunksLeft = explode(':', $chunks[0]);
396  $chunksRight = explode(':', $chunks[1]);
397  $left = count($chunksLeft);
398  $right = count($chunksRight);
399  // Special case: leading zero-only blocks count to 1, should be 0
400  if ($left === 1 && strlen($chunksLeft[0]) === 0) {
401  $left = 0;
402  }
403  $hiddenBlocks = 8 - ($left + $right);
404  $hiddenPart = '';
405  $h = 0;
406  while ($h < $hiddenBlocks) {
407  $hiddenPart .= '0000:';
408  $h++;
409  }
410  if ($left === 0) {
411  $stageOneAddress = $hiddenPart . $chunks[1];
412  } else {
413  $stageOneAddress = $chunks[0] . ':' . $hiddenPart . $chunks[1];
414  }
415  } else {
416  $stageOneAddress = $address;
417  }
418  // Normalize the blocks:
419  $blocks = explode(':', $stageOneAddress);
420  $divCounter = 0;
421  foreach ($blocks as $block) {
422  $tmpBlock = '';
423  $i = 0;
424  $hiddenZeros = 4 - strlen($block);
425  while ($i < $hiddenZeros) {
426  $tmpBlock .= '0';
427  $i++;
428  }
429  $normalizedAddress .= $tmpBlock . $block;
430  if ($divCounter < 7) {
431  $normalizedAddress .= ':';
432  $divCounter++;
433  }
434  }
435  return $normalizedAddress;
436  }
437 
446  public static function compressIPv6($address)
447  {
448  trigger_error('GeneralUtility::compressIPv6() will be removed in TYPO3 v11.0. Use the native PHP functions inet_ntop(inet_pton($address)) instead.', E_USER_DEPRECATED);
449  return inet_ntop(inet_pton($address));
450  }
451 
460  public static function validIP($ip)
461  {
462  return filter_var($ip, FILTER_VALIDATE_IP) !== false;
463  }
464 
473  public static function validIPv4($ip)
474  {
475  return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false;
476  }
477 
486  public static function validIPv6($ip)
487  {
488  return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false;
489  }
490 
498  public static function cmpFQDN($baseHost, $list)
499  {
500  $baseHost = trim($baseHost);
501  if (empty($baseHost)) {
502  return false;
503  }
504  if (self::validIPv4($baseHost) || self::validIPv6($baseHost)) {
505  // Resolve hostname
506  // Note: this is reverse-lookup and can be randomly set as soon as somebody is able to set
507  // the reverse-DNS for his IP (security when for example used with REMOTE_ADDR)
508  $baseHostName = (string)gethostbyaddr($baseHost);
509  if ($baseHostName === $baseHost) {
510  // Unable to resolve hostname
511  return false;
512  }
513  } else {
514  $baseHostName = $baseHost;
515  }
516  $baseHostNameParts = explode('.', $baseHostName);
517  $values = ‪self::trimExplode(',', $list, true);
518  foreach ($values as $test) {
519  $hostNameParts = explode('.', $test);
520  // To match hostNameParts can only be shorter (in case of wildcards) or equal
521  $hostNamePartsCount = count($hostNameParts);
522  $baseHostNamePartsCount = count($baseHostNameParts);
523  if ($hostNamePartsCount > $baseHostNamePartsCount) {
524  continue;
525  }
526  $yes = true;
527  foreach ($hostNameParts as $index => $val) {
528  $val = trim($val);
529  if ($val === '*') {
530  // Wildcard valid for one or more hostname-parts
531  $wildcardStart = $index + 1;
532  // Wildcard as last/only part always matches, otherwise perform recursive checks
533  if ($wildcardStart < $hostNamePartsCount) {
534  $wildcardMatched = false;
535  $tempHostName = implode('.', array_slice($hostNameParts, $index + 1));
536  while ($wildcardStart < $baseHostNamePartsCount && !$wildcardMatched) {
537  $tempBaseHostName = implode('.', array_slice($baseHostNameParts, $wildcardStart));
538  $wildcardMatched = self::cmpFQDN($tempBaseHostName, $tempHostName);
539  $wildcardStart++;
540  }
541  if ($wildcardMatched) {
542  // Match found by recursive compare
543  return true;
544  }
545  $yes = false;
546  }
547  } elseif ($baseHostNameParts[$index] !== $val) {
548  // In case of no match
549  $yes = false;
550  }
551  }
552  if ($yes) {
553  return true;
554  }
555  }
556  return false;
557  }
558 
566  public static function isOnCurrentHost($url)
567  {
568  return stripos($url . '/', self::getIndpEnv('TYPO3_REQUEST_HOST') . '/') === 0;
569  }
570 
579  public static function inList($list, $item)
580  {
581  return strpos(',' . $list . ',', ',' . $item . ',') !== false;
582  }
583 
594  public static function rmFromList($element, $list)
595  {
596  $items = explode(',', $list);
597  foreach ($items as $k => $v) {
598  if ($v == $element) {
599  unset($items[$k]);
600  }
601  }
602  return implode(',', $items);
603  }
604 
612  public static function expandList($list)
613  {
614  $items = explode(',', $list);
615  $list = [];
616  foreach ($items as $item) {
617  $range = explode('-', $item);
618  if (isset($range[1])) {
619  $runAwayBrake = 1000;
620  for ($n = $range[0]; $n <= $range[1]; $n++) {
621  $list[] = $n;
622  $runAwayBrake--;
623  if ($runAwayBrake <= 0) {
624  break;
625  }
626  }
627  } else {
628  $list[] = $item;
629  }
630  }
631  return implode(',', $list);
632  }
633 
640  public static function md5int($str)
641  {
642  return hexdec(substr(md5($str), 0, 7));
643  }
644 
652  public static function shortMD5($input, $len = 10)
653  {
654  return substr(md5($input), 0, $len);
655  }
656 
664  public static function hmac($input, $additionalSecret = '')
665  {
666  $hashAlgorithm = 'sha1';
667  $hashBlocksize = 64;
668  $secret = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'] . $additionalSecret;
669  if (extension_loaded('hash') && function_exists('hash_hmac') && function_exists('hash_algos') && in_array($hashAlgorithm, hash_algos())) {
670  $hmac = hash_hmac($hashAlgorithm, $input, $secret);
671  } else {
672  // Outer padding
673  $opad = str_repeat(chr(92), $hashBlocksize);
674  // Inner padding
675  $ipad = str_repeat(chr(54), $hashBlocksize);
676  if (strlen($secret) > $hashBlocksize) {
677  // Keys longer than block size are shorten
678  $key = str_pad(pack('H*', call_user_func($hashAlgorithm, $secret)), $hashBlocksize, "\0");
679  } else {
680  // Keys shorter than block size are zero-padded
681  $key = str_pad($secret, $hashBlocksize, "\0");
682  }
683  $hmac = call_user_func($hashAlgorithm, ($key ^ $opad) . pack('H*', call_user_func(
684  $hashAlgorithm,
685  ($key ^ $ipad) . $input
686  )));
687  }
688  return $hmac;
689  }
690 
699  public static function uniqueList($in_list, $secondParameter = null)
700  {
701  if (is_array($in_list)) {
702  throw new \InvalidArgumentException('TYPO3 Fatal Error: TYPO3\\CMS\\Core\\Utility\\GeneralUtility::uniqueList() does NOT support array arguments anymore! Only string comma lists!', 1270853885);
703  }
704  if (isset($secondParameter)) {
705  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);
706  }
707  return implode(',', array_unique(self::trimExplode(',', $in_list, true)));
708  }
709 
716  public static function split_fileref($fileNameWithPath)
717  {
718  $info = [];
719  $reg = [];
720  if (preg_match('/(.*\\/)(.*)$/', $fileNameWithPath, $reg)) {
721  $info['path'] = $reg[1];
722  $info['file'] = $reg[2];
723  } else {
724  $info['path'] = '';
725  $info['file'] = $fileNameWithPath;
726  }
727  $reg = '';
728  // If open_basedir is set and the fileName was supplied without a path the is_dir check fails
729  if (!is_dir($fileNameWithPath) && preg_match('/(.*)\\.([^\\.]*$)/', $info['file'], $reg)) {
730  $info['filebody'] = $reg[1];
731  $info['fileext'] = strtolower($reg[2]);
732  $info['realFileext'] = $reg[2];
733  } else {
734  $info['filebody'] = $info['file'];
735  $info['fileext'] = '';
736  }
737  reset($info);
738  return $info;
739  }
740 
756  public static function dirname($path)
757  {
758  $p = ‪self::revExplode('/', $path, 2);
759  return count($p) === 2 ? $p[0] : '';
760  }
761 
769  public static function isFirstPartOfStr($str, $partStr)
770  {
771  $str = is_array($str) ? '' : (string)$str;
772  $partStr = is_array($partStr) ? '' : (string)$partStr;
773  return $partStr !== '' && strpos($str, $partStr, 0) === 0;
774  }
775 
784  public static function formatSize(‪$sizeInBytes, $labels = '', $base = 0)
785  {
786  $defaultFormats = [
787  'iec' => ['base' => 1024, 'labels' => [' ', ' Ki', ' Mi', ' Gi', ' Ti', ' Pi', ' Ei', ' Zi', ' Yi']],
788  'si' => ['base' => 1000, 'labels' => [' ', ' k', ' M', ' G', ' T', ' P', ' E', ' Z', ' Y']],
789  ];
790  // Set labels and base:
791  if (empty($labels)) {
792  $labels = 'iec';
793  }
794  if (isset($defaultFormats[$labels])) {
795  $base = $defaultFormats[$labels]['base'];
796  ‪$labelArr = $defaultFormats[$labels]['labels'];
797  } else {
798  $base = (int)$base;
799  if ($base !== 1000 && $base !== 1024) {
800  $base = 1024;
801  }
802  ‪$labelArr = explode('|', str_replace('"', '', $labels));
803  }
804  // This is set via Site Handling and in the Locales class via setlocale()
805  ‪$localeInfo = localeconv();
807  ‪$multiplier = floor((‪$sizeInBytes ? log(‪$sizeInBytes) : 0) / log($base));
809  if (‪$sizeInUnits > ($base * .9)) {
810  ‪$multiplier++;
811  }
812  ‪$multiplier = min(‪$multiplier, count(‪$labelArr) - 1);
814  return number_format(‪$sizeInUnits, ((‪$multiplier > 0) && (‪$sizeInUnits < 20)) ? 2 : 0, ‪$localeInfo['decimal_point'], '') . ‪$labelArr[‪$multiplier];
815  }
816 
826  public static function splitCalc($string, $operators)
827  {
828  $res = [];
829  $sign = '+';
830  while ($string) {
831  $valueLen = strcspn($string, $operators);
832  $value = substr($string, 0, $valueLen);
833  $res[] = [$sign, trim($value)];
834  $sign = substr($string, $valueLen, 1);
835  $string = substr($string, $valueLen + 1);
836  }
837  reset($res);
838  return $res;
839  }
840 
847  public static function validEmail($email)
848  {
849  // Early return in case input is not a string
850  if (!is_string($email)) {
851  return false;
852  }
853  if (trim($email) !== $email) {
854  return false;
855  }
856  if (strpos($email, '@') === false) {
857  return false;
858  }
859  ‪$validator = new EmailValidator();
860  return ‪$validator->isValid($email, new RFCValidation());
861  }
862 
870  public static function ‪idnaEncode($value)
871  {
872  trigger_error(__METHOD__ . ' will be removed in TYPO3 v11.0. Use PHPs native "idn_to_ascii($domain, IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46)" function directly instead.', E_USER_DEPRECATED);
873  // Early return in case input is not a string or empty
874  if (!is_string($value) || empty($value)) {
875  return (string)$value;
876  }
877  // Split on the last "@" since addresses like "foo@bar"@example.org are valid where the only focus
878  // is an email address
879  $atPosition = strrpos($value, '@');
880  if ($atPosition !== false) {
881  $domain = substr($value, $atPosition + 1);
882  $local = substr($value, 0, $atPosition);
883  $domain = (string)‪HttpUtility::idn_to_ascii($domain);
884  // Return if no @ found or it is placed at the very beginning or end of the email
885  return $local . '@' . $domain;
886  }
887  return (string)‪HttpUtility::idn_to_ascii($value);
888  }
889 
897  public static function ‪underscoredToUpperCamelCase($string)
898  {
899  return str_replace(' ', '', ucwords(str_replace('_', ' ', strtolower($string))));
900  }
901 
909  public static function ‪underscoredToLowerCamelCase($string)
910  {
911  return lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', strtolower($string)))));
912  }
913 
921  public static function ‪camelCaseToLowerCaseUnderscored($string)
922  {
923  $value = preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $string) ?? '';
924  return mb_strtolower($value, 'utf-8');
925  }
926 
951  public static function ‪isValidUrl($url)
952  {
953  $parsedUrl = parse_url($url);
954  if (!$parsedUrl || !isset($parsedUrl['scheme'])) {
955  return false;
956  }
957  // HttpUtility::buildUrl() will always build urls with <scheme>://
958  // our original $url might only contain <scheme>: (e.g. mail:)
959  // so we convert that to the double-slashed version to ensure
960  // our check against the $recomposedUrl is proper
961  if (!self::isFirstPartOfStr($url, $parsedUrl['scheme'] . '://')) {
962  $url = str_replace($parsedUrl['scheme'] . ':', $parsedUrl['scheme'] . '://', $url);
963  }
964  $recomposedUrl = ‪HttpUtility::buildUrl($parsedUrl);
965  if ($recomposedUrl !== $url) {
966  // The parse_url() had to modify characters, so the URL is invalid
967  return false;
968  }
969  if (isset($parsedUrl['host']) && !preg_match('/^[a-z0-9.\\-]*$/i', $parsedUrl['host'])) {
970  $host = ‪HttpUtility::idn_to_ascii($parsedUrl['host']);
971  if ($host === false) {
972  return false;
973  }
974  $parsedUrl['host'] = $host;
975  }
976  return filter_var(‪HttpUtility::buildUrl($parsedUrl), FILTER_VALIDATE_URL) !== false;
977  }
978 
979  /*************************
980  *
981  * ARRAY FUNCTIONS
982  *
983  *************************/
984 
995  public static function ‪intExplode($delimiter, $string, $removeEmptyValues = false, $limit = 0)
996  {
997  $result = explode($delimiter, $string) ?: [];
998  foreach ($result as $key => &$value) {
999  if ($removeEmptyValues && ($value === '' || trim($value) === '')) {
1000  unset($result[$key]);
1001  } else {
1002  $value = (int)$value;
1003  }
1004  }
1005  unset($value);
1006  if ($limit !== 0) {
1007  if ($limit < 0) {
1008  $result = array_slice($result, 0, $limit);
1009  } elseif (count($result) > $limit) {
1010  $lastElements = array_slice($result, $limit - 1);
1011  $result = array_slice($result, 0, $limit - 1);
1012  $result[] = implode($delimiter, $lastElements);
1013  }
1014  }
1015  return $result;
1016  }
1017 
1032  public static function ‪revExplode($delimiter, $string, $count = 0)
1033  {
1034  // 2 is the (currently, as of 2014-02) most-used value for $count in the core, therefore we check it first
1035  if ($count === 2) {
1036  $position = strrpos($string, strrev($delimiter));
1037  if ($position !== false) {
1038  return [substr($string, 0, $position), substr($string, $position + strlen($delimiter))];
1039  }
1040  return [$string];
1041  }
1042  if ($count <= 1) {
1043  return [$string];
1044  }
1045  $explodedValues = explode($delimiter, strrev($string), $count) ?: [];
1046  $explodedValues = array_map('strrev', $explodedValues);
1047  return array_reverse($explodedValues);
1048  }
1049 
1066  public static function ‪trimExplode($delim, $string, $removeEmptyValues = false, $limit = 0): array
1067  {
1068  $result = explode($delim, $string) ?: [];
1069  if ($removeEmptyValues) {
1070  // Remove items that are just whitespace, but leave whitespace intact for the rest.
1071  $result = array_values(array_filter($result, function ($item) {return trim($item) !== '';}));
1072  }
1073 
1074  if ($limit === 0) {
1075  // Return everything.
1076  return array_map('trim', $result);
1077  }
1078 
1079  if ($limit < 0) {
1080  // Trim and return just the first $limit elements and ignore the rest.
1081  return array_map('trim', array_slice($result, 0, $limit));
1082  }
1083 
1084  // Fold the last length - $limit elements into a single trailing item, then trim and return the result.
1085  $tail = array_slice($result, $limit - 1);
1086  $result = array_slice($result, 0, $limit - 1);
1087  if ($tail) {
1088  $result[] = implode($delim, $tail);
1089  }
1090  return array_map('trim', $result);
1091  }
1092 
1104  public static function ‪implodeArrayForUrl($name, array $theArray, $str = '', $skipBlank = false, $rawurlencodeParamName = false)
1105  {
1106  foreach ($theArray as $Akey => $AVal) {
1107  $thisKeyName = $name ? $name . '[' . $Akey . ']' : $Akey;
1108  if (is_array($AVal)) {
1109  $str = ‪self::implodeArrayForUrl($thisKeyName, $AVal, $str, $skipBlank, $rawurlencodeParamName);
1110  } else {
1111  if (!$skipBlank || (string)$AVal !== '') {
1112  $str .= '&' . ($rawurlencodeParamName ? rawurlencode($thisKeyName) : $thisKeyName) . '=' . rawurlencode($AVal);
1113  }
1114  }
1115  }
1116  return $str;
1117  }
1118 
1134  public static function explodeUrl2Array($string)
1135  {
1136  ‪$output = [];
1137  $p = explode('&', $string);
1138  foreach ($p as $v) {
1139  if ($v !== '') {
1140  [$pK, $pV] = explode('=', $v, 2);
1141  ‪$output[rawurldecode($pK)] = rawurldecode($pV);
1142  }
1143  }
1144  return ‪$output;
1145  }
1146 
1156  public static function compileSelectedGetVarsFromArray($varList, array $getArray, $GPvarAlt = true)
1157  {
1158  $keys = ‪self::trimExplode(',', $varList, true);
1159  $outArr = [];
1160  foreach ($keys as $v) {
1161  if (isset($getArray[$v])) {
1162  $outArr[$v] = $getArray[$v];
1163  } elseif ($GPvarAlt) {
1164  $outArr[$v] = self::_GP($v);
1165  }
1166  }
1167  return $outArr;
1168  }
1169 
1177  public static function removeDotsFromTS(array $ts)
1178  {
1179  $out = [];
1180  foreach ($ts as $key => $value) {
1181  if (is_array($value)) {
1182  $key = rtrim($key, '.');
1183  $out[$key] = self::removeDotsFromTS($value);
1184  } else {
1185  $out[$key] = $value;
1186  }
1187  }
1188  return $out;
1189  }
1190 
1191  /*************************
1192  *
1193  * HTML/XML PROCESSING
1194  *
1195  *************************/
1205  public static function get_tag_attributes($tag, bool $decodeEntities = false)
1206  {
1207  $components = self::split_tag_attributes($tag);
1208  // Attribute name is stored here
1209  $name = '';
1210  $valuemode = false;
1211  $attributes = [];
1212  foreach ($components as $key => $val) {
1213  // 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
1214  if ($val !== '=') {
1215  if ($valuemode) {
1216  if ($name) {
1217  $attributes[$name] = $decodeEntities ? htmlspecialchars_decode($val) : $val;
1218  $name = '';
1219  }
1220  } else {
1221  if ($key = strtolower(preg_replace('/[^[:alnum:]_\\:\\-]/', '', $val) ?? '')) {
1222  $attributes[$key] = '';
1223  $name = $key;
1224  }
1225  }
1226  $valuemode = false;
1227  } else {
1228  $valuemode = true;
1229  }
1230  }
1231  return $attributes;
1232  }
1233 
1241  public static function split_tag_attributes($tag)
1242  {
1243  $tag_tmp = trim(preg_replace('/^<[^[:space:]]*/', '', trim($tag)) ?? '');
1244  // Removes any > in the end of the string
1245  $tag_tmp = trim(rtrim($tag_tmp, '>'));
1246  $value = [];
1247  // Compared with empty string instead , 030102
1248  while ($tag_tmp !== '') {
1249  $firstChar = $tag_tmp[0];
1250  if ($firstChar === '"' || $firstChar === '\'') {
1251  $reg = explode($firstChar, $tag_tmp, 3);
1252  $value[] = $reg[1];
1253  $tag_tmp = trim($reg[2]);
1254  } elseif ($firstChar === '=') {
1255  $value[] = '=';
1256  // Removes = chars.
1257  $tag_tmp = trim(substr($tag_tmp, 1));
1258  } else {
1259  // There are '' around the value. We look for the next ' ' or '>'
1260  $reg = preg_split('/[[:space:]=]/', $tag_tmp, 2);
1261  $value[] = trim($reg[0]);
1262  $tag_tmp = trim(substr($tag_tmp, strlen($reg[0]), 1) . ($reg[1] ?? ''));
1263  }
1264  }
1265  reset($value);
1266  return $value;
1267  }
1268 
1277  public static function implodeAttributes(array $arr, $xhtmlSafe = false, $dontOmitBlankAttribs = false)
1278  {
1279  if ($xhtmlSafe) {
1280  $newArr = [];
1281  foreach ($arr as $p => $v) {
1282  if (!isset($newArr[strtolower($p)])) {
1283  $newArr[strtolower($p)] = htmlspecialchars($v);
1284  }
1285  }
1286  $arr = $newArr;
1287  }
1288  $list = [];
1289  foreach ($arr as $p => $v) {
1290  if ((string)$v !== '' || $dontOmitBlankAttribs) {
1291  $list[] = $p . '="' . $v . '"';
1292  }
1293  }
1294  return implode(' ', $list);
1295  }
1296 
1305  public static function wrapJS($string)
1306  {
1307  if (trim($string)) {
1308  // remove nl from the beginning
1309  $string = ltrim($string, LF);
1310  // re-ident to one tab using the first line as reference
1311  $match = [];
1312  if (preg_match('/^(\\t+)/', $string, $match)) {
1313  $string = str_replace($match[1], "\t", $string);
1314  }
1315  return '<script>
1316 /*<![CDATA[*/
1317 ' . $string . '
1318 /*]]>*/
1319 </script>';
1320  }
1321  return '';
1322  }
1323 
1332  public static function xml2tree($string, $depth = 999, $parserOptions = [])
1333  {
1334  // Disables the functionality to allow external entities to be loaded when parsing the XML, must be kept
1335  $previousValueOfEntityLoader = null;
1336  if (PHP_MAJOR_VERSION < 8) {
1337  $previousValueOfEntityLoader = libxml_disable_entity_loader(true);
1338  }
1339  ‪$parser = xml_parser_create();
1340  $vals = [];
1341  $index = [];
1342  xml_parser_set_option(‪$parser, XML_OPTION_CASE_FOLDING, 0);
1343  xml_parser_set_option(‪$parser, XML_OPTION_SKIP_WHITE, 0);
1344  foreach ($parserOptions as $option => $value) {
1345  xml_parser_set_option(‪$parser, $option, $value);
1346  }
1347  xml_parse_into_struct(‪$parser, $string, $vals, $index);
1348  if (PHP_MAJOR_VERSION < 8) {
1349  libxml_disable_entity_loader($previousValueOfEntityLoader);
1350  }
1351  if (xml_get_error_code(‪$parser)) {
1352  return 'Line ' . xml_get_current_line_number(‪$parser) . ': ' . xml_error_string(xml_get_error_code(‪$parser));
1353  }
1354  xml_parser_free(‪$parser);
1355  $stack = [[]];
1356  $stacktop = 0;
1357  $startPoint = 0;
1358  $tagi = [];
1359  foreach ($vals as $key => $val) {
1360  $type = $val['type'];
1361  // open tag:
1362  if ($type === 'open' || $type === 'complete') {
1363  $stack[$stacktop++] = $tagi;
1364  if ($depth == $stacktop) {
1365  $startPoint = $key;
1366  }
1367  $tagi = ['tag' => $val['tag']];
1368  if (isset($val['attributes'])) {
1369  $tagi['attrs'] = $val['attributes'];
1370  }
1371  if (isset($val['value'])) {
1372  $tagi['values'][] = $val['value'];
1373  }
1374  }
1375  // finish tag:
1376  if ($type === 'complete' || $type === 'close') {
1377  $oldtagi = $tagi;
1378  $tagi = $stack[--$stacktop];
1379  $oldtag = $oldtagi['tag'];
1380  unset($oldtagi['tag']);
1381  if ($depth == $stacktop + 1) {
1382  if ($key - $startPoint > 0) {
1383  $partArray = array_slice($vals, $startPoint + 1, $key - $startPoint - 1);
1384  $oldtagi['XMLvalue'] = ‪self::xmlRecompileFromStructValArray($partArray);
1385  } else {
1386  $oldtagi['XMLvalue'] = $oldtagi['values'][0];
1387  }
1388  }
1389  $tagi['ch'][$oldtag][] = $oldtagi;
1390  unset($oldtagi);
1391  }
1392  // cdata
1393  if ($type === 'cdata') {
1394  $tagi['values'][] = $val['value'];
1395  }
1396  }
1397  return $tagi['ch'];
1398  }
1399 
1420  public static function array2xml(array $array, $NSprefix = '', $level = 0, $docTag = 'phparray', ‪$spaceInd = 0, array $options = [], array $stackData = [])
1421  {
1422  // 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
1423  $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);
1424  // Set indenting mode:
1425  $indentChar = ‪$spaceInd ? ' ' : "\t";
1426  $indentN = ‪$spaceInd > 0 ? ‪$spaceInd : 1;
1427  ‪$nl = ‪$spaceInd >= 0 ? LF : '';
1428  // Init output variable:
1429  ‪$output = '';
1430  // Traverse the input array
1431  foreach ($array as $k => $v) {
1432  $attr = '';
1433  $tagName = $k;
1434  // Construct the tag name.
1435  // Use tag based on grand-parent + parent tag name
1436  if (isset($stackData['grandParentTagName'], $stackData['parentTagName'], $options['grandParentTagMap'][$stackData['grandParentTagName'] . '/' . $stackData['parentTagName']])) {
1437  $attr .= ' index="' . htmlspecialchars($tagName) . '"';
1438  $tagName = (string)$options['grandParentTagMap'][$stackData['grandParentTagName'] . '/' . $stackData['parentTagName']];
1439  } elseif (isset($stackData['parentTagName'], $options['parentTagMap'][$stackData['parentTagName'] . ':_IS_NUM']) && ‪MathUtility::canBeInterpretedAsInteger($tagName)) {
1440  // Use tag based on parent tag name + if current tag is numeric
1441  $attr .= ' index="' . htmlspecialchars($tagName) . '"';
1442  $tagName = (string)$options['parentTagMap'][$stackData['parentTagName'] . ':_IS_NUM'];
1443  } elseif (isset($stackData['parentTagName'], $options['parentTagMap'][$stackData['parentTagName'] . ':' . $tagName])) {
1444  // Use tag based on parent tag name + current tag
1445  $attr .= ' index="' . htmlspecialchars($tagName) . '"';
1446  $tagName = (string)$options['parentTagMap'][$stackData['parentTagName'] . ':' . $tagName];
1447  } elseif (isset($stackData['parentTagName'], $options['parentTagMap'][$stackData['parentTagName']])) {
1448  // Use tag based on parent tag name:
1449  $attr .= ' index="' . htmlspecialchars($tagName) . '"';
1450  $tagName = (string)$options['parentTagMap'][$stackData['parentTagName']];
1451  } elseif (‪MathUtility::canBeInterpretedAsInteger($tagName)) {
1452  // If integer...;
1453  if ($options['useNindex']) {
1454  // If numeric key, prefix "n"
1455  $tagName = 'n' . $tagName;
1456  } else {
1457  // Use special tag for num. keys:
1458  $attr .= ' index="' . $tagName . '"';
1459  $tagName = $options['useIndexTagForNum'] ?: 'numIndex';
1460  }
1461  } elseif (!empty($options['useIndexTagForAssoc'])) {
1462  // Use tag for all associative keys:
1463  $attr .= ' index="' . htmlspecialchars($tagName) . '"';
1464  $tagName = $options['useIndexTagForAssoc'];
1465  }
1466  // The tag name is cleaned up so only alphanumeric chars (plus - and _) are in there and not longer than 100 chars either.
1467  $tagName = substr(preg_replace('/[^[:alnum:]_-]/', '', $tagName), 0, 100);
1468  // If the value is an array then we will call this function recursively:
1469  if (is_array($v)) {
1470  // Sub elements:
1471  if (isset($options['alt_options']) && $options['alt_options'][($stackData['path'] ?? '') . '/' . $tagName]) {
1472  $subOptions = $options['alt_options'][$stackData['path'] . '/' . $tagName];
1473  $clearStackPath = $subOptions['clearStackPath'];
1474  } else {
1475  $subOptions = $options;
1476  $clearStackPath = false;
1477  }
1478  if (empty($v)) {
1479  $content = '';
1480  } else {
1481  $content = ‪$nl . self::array2xml($v, $NSprefix, $level + 1, '', ‪$spaceInd, $subOptions, [
1482  'parentTagName' => $tagName,
1483  'grandParentTagName' => $stackData['parentTagName'] ?? '',
1484  'path' => $clearStackPath ? '' : ($stackData['path'] ?? '') . '/' . $tagName
1485  ]) . (‪$spaceInd >= 0 ? str_pad('', ($level + 1) * $indentN, $indentChar) : '');
1486  }
1487  // Do not set "type = array". Makes prettier XML but means that empty arrays are not restored with xml2array
1488  if (!isset($options['disableTypeAttrib']) || (int)$options['disableTypeAttrib'] != 2) {
1489  $attr .= ' type="array"';
1490  }
1491  } else {
1492  // Just a value:
1493  // Look for binary chars:
1494  $vLen = strlen($v);
1495  // Go for base64 encoding if the initial segment NOT matching any binary char has the same length as the whole string!
1496  if ($vLen && strcspn($v, $binaryChars) != $vLen) {
1497  // If the value contained binary chars then we base64-encode it and set an attribute to notify this situation:
1498  $content = ‪$nl . chunk_split(base64_encode($v));
1499  $attr .= ' base64="1"';
1500  } else {
1501  // Otherwise, just htmlspecialchar the stuff:
1502  $content = htmlspecialchars($v);
1503  $dType = gettype($v);
1504  if ($dType === 'string') {
1505  if (isset($options['useCDATA']) && $options['useCDATA'] && $content != $v) {
1506  $content = '<![CDATA[' . $v . ']]>';
1507  }
1508  } elseif (!$options['disableTypeAttrib']) {
1509  $attr .= ' type="' . $dType . '"';
1510  }
1511  }
1512  }
1513  if ((string)$tagName !== '') {
1514  // Add the element to the output string:
1515  ‪$output .= (‪$spaceInd >= 0 ? str_pad('', ($level + 1) * $indentN, $indentChar) : '')
1516  . '<' . $NSprefix . $tagName . $attr . '>' . $content . '</' . $NSprefix . $tagName . '>' . ‪$nl;
1517  }
1518  }
1519  // If we are at the outer-most level, then we finally wrap it all in the document tags and return that as the value:
1520  if (!$level) {
1521  ‪$output = '<' . $docTag . '>' . ‪$nl . ‪$output . '</' . $docTag . '>';
1522  }
1523  return ‪$output;
1524  }
1525 
1538  public static function ‪xml2array($string, $NSprefix = '', $reportDocTag = false)
1539  {
1540  $runtimeCache = static::makeInstance(CacheManager::class)->getCache('runtime');
1541  $firstLevelCache = $runtimeCache->get('generalUtilityXml2Array') ?: [];
1542  $identifier = md5($string . $NSprefix . ($reportDocTag ? '1' : '0'));
1543  // Look up in first level cache
1544  if (empty($firstLevelCache[$identifier])) {
1545  $firstLevelCache[$identifier] = ‪self::xml2arrayProcess($string, $NSprefix, $reportDocTag);
1546  $runtimeCache->set('generalUtilityXml2Array', $firstLevelCache);
1547  }
1548  return $firstLevelCache[$identifier];
1549  }
1550 
1561  public static function ‪xml2arrayProcess($string, $NSprefix = '', $reportDocTag = false)
1562  {
1563  $string = trim($string);
1564  // Disables the functionality to allow external entities to be loaded when parsing the XML, must be kept
1565  $previousValueOfEntityLoader = null;
1566  if (PHP_MAJOR_VERSION < 8) {
1567  $previousValueOfEntityLoader = libxml_disable_entity_loader(true);
1568  }
1569  // Create parser:
1570  ‪$parser = xml_parser_create();
1571  $vals = [];
1572  $index = [];
1573  xml_parser_set_option(‪$parser, XML_OPTION_CASE_FOLDING, 0);
1574  xml_parser_set_option(‪$parser, XML_OPTION_SKIP_WHITE, 0);
1575  // Default output charset is UTF-8, only ASCII, ISO-8859-1 and UTF-8 are supported!!!
1576  $match = [];
1577  preg_match('/^[[:space:]]*<\\?xml[^>]*encoding[[:space:]]*=[[:space:]]*"([^"]*)"/', substr($string, 0, 200), $match);
1578  $theCharset = $match[1] ?? 'utf-8';
1579  // us-ascii / utf-8 / iso-8859-1
1580  xml_parser_set_option(‪$parser, XML_OPTION_TARGET_ENCODING, $theCharset);
1581  // Parse content:
1582  xml_parse_into_struct(‪$parser, $string, $vals, $index);
1583  if (PHP_MAJOR_VERSION < 8) {
1584  libxml_disable_entity_loader($previousValueOfEntityLoader);
1585  }
1586  // If error, return error message:
1587  if (xml_get_error_code(‪$parser)) {
1588  return 'Line ' . xml_get_current_line_number(‪$parser) . ': ' . xml_error_string(xml_get_error_code(‪$parser));
1589  }
1590  xml_parser_free(‪$parser);
1591  // Init vars:
1592  $stack = [[]];
1593  $stacktop = 0;
1594  $current = [];
1595  $tagName = '';
1596  $documentTag = '';
1597  // Traverse the parsed XML structure:
1598  foreach ($vals as $key => $val) {
1599  // First, process the tag-name (which is used in both cases, whether "complete" or "close")
1600  $tagName = $val['tag'];
1601  if (!$documentTag) {
1602  $documentTag = $tagName;
1603  }
1604  // Test for name space:
1605  $tagName = $NSprefix && strpos($tagName, $NSprefix) === 0 ? substr($tagName, strlen($NSprefix)) : $tagName;
1606  // Test for numeric tag, encoded on the form "nXXX":
1607  $testNtag = substr($tagName, 1);
1608  // Closing tag.
1609  $tagName = $tagName[0] === 'n' && ‪MathUtility::canBeInterpretedAsInteger($testNtag) ? (int)$testNtag : $tagName;
1610  // Test for alternative index value:
1611  if ((string)($val['attributes']['index'] ?? '') !== '') {
1612  $tagName = $val['attributes']['index'];
1613  }
1614  // Setting tag-values, manage stack:
1615  switch ($val['type']) {
1616  case 'open':
1617  // If open tag it means there is an array stored in sub-elements. Therefore increase the stackpointer and reset the accumulation array:
1618  // Setting blank place holder
1619  $current[$tagName] = [];
1620  $stack[$stacktop++] = $current;
1621  $current = [];
1622  break;
1623  case 'close':
1624  // If the tag is "close" then it is an array which is closing and we decrease the stack pointer.
1625  $oldCurrent = $current;
1626  $current = $stack[--$stacktop];
1627  // Going to the end of array to get placeholder key, key($current), and fill in array next:
1628  end($current);
1629  $current[key($current)] = $oldCurrent;
1630  unset($oldCurrent);
1631  break;
1632  case 'complete':
1633  // If "complete", then it's a value. If the attribute "base64" is set, then decode the value, otherwise just set it.
1634  if (!empty($val['attributes']['base64'])) {
1635  $current[$tagName] = base64_decode($val['value']);
1636  } else {
1637  // Had to cast it as a string - otherwise it would be evaluate FALSE if tested with isset()!!
1638  $current[$tagName] = (string)($val['value'] ?? '');
1639  // Cast type:
1640  switch ((string)($val['attributes']['type'] ?? '')) {
1641  case 'integer':
1642  $current[$tagName] = (int)$current[$tagName];
1643  break;
1644  case 'double':
1645  $current[$tagName] = (double)$current[$tagName];
1646  break;
1647  case 'boolean':
1648  $current[$tagName] = (bool)$current[$tagName];
1649  break;
1650  case 'NULL':
1651  $current[$tagName] = null;
1652  break;
1653  case 'array':
1654  // 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...
1655  $current[$tagName] = [];
1656  break;
1657  }
1658  }
1659  break;
1660  }
1661  }
1662  if ($reportDocTag) {
1663  $current[$tagName]['_DOCUMENT_TAG'] = $documentTag;
1664  }
1665  // Finally return the content of the document tag.
1666  return $current[$tagName];
1667  }
1675  public static function ‪xmlRecompileFromStructValArray(array $vals)
1676  {
1677  $XMLcontent = '';
1678  foreach ($vals as $val) {
1679  $type = $val['type'];
1680  // Open tag:
1681  if ($type === 'open' || $type === 'complete') {
1682  $XMLcontent .= '<' . $val['tag'];
1683  if (isset($val['attributes'])) {
1684  foreach ($val['attributes'] as $k => $v) {
1685  $XMLcontent .= ' ' . $k . '="' . htmlspecialchars($v) . '"';
1686  }
1687  }
1688  if ($type === 'complete') {
1689  if (isset($val['value'])) {
1690  $XMLcontent .= '>' . htmlspecialchars($val['value']) . '</' . $val['tag'] . '>';
1691  } else {
1692  $XMLcontent .= '/>';
1693  }
1694  } else {
1695  $XMLcontent .= '>';
1696  }
1697  if ($type === 'open' && isset($val['value'])) {
1698  $XMLcontent .= htmlspecialchars($val['value']);
1699  }
1700  }
1701  // Finish tag:
1702  if ($type === 'close') {
1703  $XMLcontent .= '</' . $val['tag'] . '>';
1704  }
1705  // Cdata
1706  if ($type === 'cdata') {
1707  $XMLcontent .= htmlspecialchars($val['value']);
1708  }
1709  }
1710  return $XMLcontent;
1711  }
1712 
1720  public static function ‪minifyJavaScript($script, &$error = '')
1721  {
1722  $fakeThis = null;
1723  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_div.php']['minifyJavaScript'] ?? [] as $hookMethod) {
1724  try {
1725  $parameters = ['script' => $script];
1726  $script = static::callUserFunction($hookMethod, $parameters, $fakeThis);
1727  } catch (\‪Exception $e) {
1728  $errorMessage = 'Error minifying java script: ' . $e->getMessage();
1729  $error .= $errorMessage;
1730  static::getLogger()->warning($errorMessage, [
1731  'JavaScript' => $script,
1732  'hook' => $hookMethod,
1733  'exception' => $e,
1734  ]);
1735  }
1736  }
1737  return $script;
1738  }
1739 
1740  /*************************
1741  *
1742  * FILES FUNCTIONS
1743  *
1744  *************************/
1755  public static function ‪getUrl($url, $includeHeader = 0, $requestHeaders = null, &$report = null)
1756  {
1757  if (func_num_args() > 1) {
1758  trigger_error('Calling GeneralUtility::getUrl() with more than one argument will not be supported anymore in TYPO3 v11.0. Use RequestFactory and PSR-7 Requests and Response objects to evaluate the results in detail. For local files, use file_get_contents directly.', E_USER_DEPRECATED);
1759  }
1760  if (isset($report)) {
1761  $report['error'] = 0;
1762  $report['message'] = '';
1763  }
1764  // Looks like it's an external file, use Guzzle by default
1765  if (preg_match('/^(?:http|ftp)s?|s(?:ftp|cp):/', $url)) {
1766  $requestFactory = static::makeInstance(RequestFactory::class);
1767  if (is_array($requestHeaders)) {
1768  $configuration = ['headers' => $requestHeaders];
1769  } else {
1770  $configuration = [];
1771  }
1772  $includeHeader = (int)$includeHeader;
1773  $method = $includeHeader === 2 ? 'HEAD' : 'GET';
1774  try {
1775  if (isset($report)) {
1776  $report['lib'] = 'GuzzleHttp';
1777  }
1778  $response = $requestFactory->request($url, $method, $configuration);
1779  } catch (RequestException $exception) {
1780  if (isset($report)) {
1781  $report['error'] = $exception->getCode() ?: 1518707554;
1782  $report['message'] = $exception->getMessage();
1783  $report['exception'] = $exception;
1784  }
1785  return false;
1786  }
1787  $content = '';
1788  // Add the headers to the output
1789  if ($includeHeader) {
1790  $parsedURL = parse_url($url);
1791  $content = $method . ' ' . ($parsedURL['path'] ?? '/')
1792  . (!empty($parsedURL['query']) ? '?' . $parsedURL['query'] : '') . ' HTTP/1.0' . CRLF
1793  . 'Host: ' . $parsedURL['host'] . CRLF
1794  . 'Connection: close' . CRLF;
1795  if (is_array($requestHeaders)) {
1796  $content .= implode(CRLF, $requestHeaders) . CRLF;
1797  }
1798  foreach ($response->getHeaders() as $headerName => $headerValues) {
1799  $content .= $headerName . ': ' . implode(', ', $headerValues) . CRLF;
1800  }
1801  // Headers are separated from the body with two CRLFs
1802  $content .= CRLF;
1803  }
1804 
1805  $content .= $response->getBody()->getContents();
1806 
1807  if (isset($report)) {
1808  if ($response->getStatusCode() >= 300 && $response->getStatusCode() < 400) {
1809  $report['http_code'] = $response->getStatusCode();
1810  $report['content_type'] = $response->getHeaderLine('Content-Type');
1811  $report['error'] = $response->getStatusCode();
1812  $report['message'] = $response->getReasonPhrase();
1813  } elseif (empty($content)) {
1814  $report['error'] = $response->getStatusCode();
1815  $report['message'] = $response->getReasonPhrase();
1816  } elseif ($includeHeader) {
1817  // Set only for $includeHeader to work exactly like PHP variant
1818  $report['http_code'] = $response->getStatusCode();
1819  $report['content_type'] = $response->getHeaderLine('Content-Type');
1820  }
1821  }
1822  } else {
1823  if (isset($report)) {
1824  $report['lib'] = 'file';
1825  }
1826  $content = @file_get_contents($url);
1827  if ($content === false && isset($report)) {
1828  $report['error'] = -1;
1829  $report['message'] = 'Couldn\'t get URL: ' . $url;
1830  }
1831  }
1832  return $content;
1833  }
1834 
1843  public static function ‪writeFile($file, $content, $changePermissions = false)
1844  {
1845  if (!@is_file($file)) {
1846  $changePermissions = true;
1847  }
1848  if ($fd = fopen($file, 'wb')) {
1849  $res = fwrite($fd, $content);
1850  fclose($fd);
1851  if ($res === false) {
1852  return false;
1853  }
1854  // Change the permissions only if the file has just been created
1855  if ($changePermissions) {
1856  static::fixPermissions($file);
1857  }
1858  return true;
1859  }
1860  return false;
1861  }
1862 
1870  public static function ‪fixPermissions($path, $recursive = false)
1871  {
1872  $targetPermissions = null;
1873  if (‪Environment::isWindows()) {
1874  return true;
1875  }
1876  $result = false;
1877  // Make path absolute
1878  if (!static::isAbsPath($path)) {
1879  $path = static::getFileAbsFileName($path);
1880  }
1881  if (static::isAllowedAbsPath($path)) {
1882  if (@is_file($path)) {
1883  $targetPermissions = (string)(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['fileCreateMask'] ?? '0644');
1884  } elseif (@is_dir($path)) {
1885  $targetPermissions = (string)(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask'] ?? '0755');
1886  }
1887  if (!empty($targetPermissions)) {
1888  // make sure it's always 4 digits
1889  $targetPermissions = str_pad($targetPermissions, 4, '0', STR_PAD_LEFT);
1890  $targetPermissions = octdec($targetPermissions);
1891  // "@" is there because file is not necessarily OWNED by the user
1892  $result = @chmod($path, $targetPermissions);
1893  }
1894  // Set createGroup if not empty
1895  if (
1896  isset(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['createGroup'])
1897  && ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['createGroup'] !== ''
1898  ) {
1899  // "@" is there because file is not necessarily OWNED by the user
1900  $changeGroupResult = @chgrp($path, ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['createGroup']);
1901  $result = $changeGroupResult ? $result : false;
1902  }
1903  // Call recursive if recursive flag if set and $path is directory
1904  if ($recursive && @is_dir($path)) {
1905  $handle = opendir($path);
1906  if (is_resource($handle)) {
1907  while (($file = readdir($handle)) !== false) {
1908  $recursionResult = null;
1909  if ($file !== '.' && $file !== '..') {
1910  if (@is_file($path . '/' . $file)) {
1911  $recursionResult = static::fixPermissions($path . '/' . $file);
1912  } elseif (@is_dir($path . '/' . $file)) {
1913  $recursionResult = static::fixPermissions($path . '/' . $file, true);
1914  }
1915  if (isset($recursionResult) && !$recursionResult) {
1916  $result = false;
1917  }
1918  }
1919  }
1920  closedir($handle);
1921  }
1922  }
1923  }
1924  return $result;
1925  }
1926 
1935  public static function ‪writeFileToTypo3tempDir($filepath, $content)
1936  {
1937  // Parse filepath into directory and basename:
1938  $fI = pathinfo($filepath);
1939  $fI['dirname'] .= '/';
1940  // Check parts:
1941  if (!static::validPathStr($filepath) || !$fI['basename'] || strlen($fI['basename']) >= 60) {
1942  return 'Input filepath "' . $filepath . '" was generally invalid!';
1943  }
1944 
1945  // Setting main temporary directory name (standard)
1946  $allowedPathPrefixes = [
1947  ‪Environment::getPublicPath() . '/typo3temp' => 'Environment::getPublicPath() + "/typo3temp/"'
1948  ];
1949  // Also allow project-path + /var/
1950  if (‪Environment::getVarPath() !== ‪Environment::getPublicPath() . '/typo3temp/var') {
1951  $relPath = substr(‪Environment::getVarPath(), strlen(‪Environment::getProjectPath()) + 1);
1952  $allowedPathPrefixes[‪Environment::getVarPath()] = 'ProjectPath + ' . $relPath;
1953  }
1954 
1955  $errorMessage = null;
1956  foreach ($allowedPathPrefixes as $pathPrefix => $prefixLabel) {
1957  $dirName = $pathPrefix . '/';
1958  // Invalid file path, let's check for the other path, if it exists
1959  if (!static::isFirstPartOfStr($fI['dirname'], $dirName)) {
1960  if ($errorMessage === null) {
1961  $errorMessage = '"' . $fI['dirname'] . '" was not within directory ' . $prefixLabel;
1962  }
1963  continue;
1964  }
1965  // This resets previous error messages from the first path
1966  $errorMessage = null;
1967 
1968  if (!@is_dir($dirName)) {
1969  $errorMessage = $prefixLabel . ' was not a directory!';
1970  // continue and see if the next iteration resets the errorMessage above
1971  continue;
1972  }
1973  // Checking if the "subdir" is found
1974  $subdir = substr($fI['dirname'], strlen($dirName));
1975  if ($subdir) {
1976  if (preg_match('#^(?:[[:alnum:]_]+/)+$#', $subdir)) {
1977  $dirName .= $subdir;
1978  if (!@is_dir($dirName)) {
1979  static::mkdir_deep($pathPrefix . '/' . $subdir);
1980  }
1981  } else {
1982  $errorMessage = 'Subdir, "' . $subdir . '", was NOT on the form "[[:alnum:]_]/+"';
1983  break;
1984  }
1985  }
1986  // Checking dir-name again (sub-dir might have been created)
1987  if (@is_dir($dirName)) {
1988  if ($filepath === $dirName . $fI['basename']) {
1989  static::writeFile($filepath, $content);
1990  if (!@is_file($filepath)) {
1991  $errorMessage = 'The file was not written to the disk. Please, check that you have write permissions to the ' . $prefixLabel . ' directory.';
1992  }
1993  break;
1994  }
1995  $errorMessage = 'Calculated file location didn\'t match input "' . $filepath . '".';
1996  break;
1997  }
1998  $errorMessage = '"' . $dirName . '" is not a directory!';
1999  break;
2000  }
2001  return $errorMessage;
2002  }
2003 
2012  public static function ‪mkdir($newFolder)
2013  {
2014  $result = @‪mkdir($newFolder, octdec(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask']));
2015  if ($result) {
2016  static::fixPermissions($newFolder);
2017  }
2018  return $result;
2019  }
2020 
2029  public static function ‪mkdir_deep($directory)
2030  {
2031  if (!is_string($directory)) {
2032  throw new \InvalidArgumentException('The specified directory is of type "' . gettype($directory) . '" but a string is expected.', 1303662955);
2033  }
2034  // Ensure there is only one slash
2035  $fullPath = rtrim($directory, '/') . '/';
2036  if ($fullPath !== '/' && !is_dir($fullPath)) {
2037  $firstCreatedPath = static::createDirectoryPath($fullPath);
2038  if ($firstCreatedPath !== '') {
2039  static::fixPermissions($firstCreatedPath, true);
2040  }
2041  }
2042  }
2043 
2055  protected static function ‪createDirectoryPath($fullDirectoryPath)
2056  {
2057  $currentPath = $fullDirectoryPath;
2058  $firstCreatedPath = '';
2059  $permissionMask = octdec(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask']);
2060  if (!@is_dir($currentPath)) {
2061  do {
2062  $firstCreatedPath = $currentPath;
2063  $separatorPosition = (int)strrpos($currentPath, DIRECTORY_SEPARATOR);
2064  $currentPath = substr($currentPath, 0, $separatorPosition);
2065  } while (!is_dir($currentPath) && $separatorPosition > 0);
2066  $result = @‪mkdir($fullDirectoryPath, $permissionMask, true);
2067  // Check existence of directory again to avoid race condition. Directory could have get created by another process between previous is_dir() and mkdir()
2068  if (!$result && !@is_dir($fullDirectoryPath)) {
2069  throw new \RuntimeException('Could not create directory "' . $fullDirectoryPath . '"!', 1170251401);
2070  }
2071  }
2072  return $firstCreatedPath;
2073  }
2074 
2082  public static function ‪rmdir($path, $removeNonEmpty = false)
2083  {
2084  $OK = false;
2085  // Remove trailing slash
2086  $path = preg_replace('|/$|', '', $path) ?? '';
2087  $isWindows = DIRECTORY_SEPARATOR === '\\';
2088  if (file_exists($path)) {
2089  $OK = true;
2090  if (!is_link($path) && is_dir($path)) {
2091  if ($removeNonEmpty === true && ($handle = @opendir($path))) {
2092  $entries = [];
2093 
2094  while (false !== ($file = readdir($handle))) {
2095  if ($file === '.' || $file === '..') {
2096  continue;
2097  }
2098 
2099  $entries[] = $path . '/' . $file;
2100  }
2101 
2102  closedir($handle);
2103 
2104  foreach ($entries as $entry) {
2105  if (!static::rmdir($entry, $removeNonEmpty)) {
2106  $OK = false;
2107  }
2108  }
2109  }
2110  if ($OK) {
2111  $OK = @‪rmdir($path);
2112  }
2113  } elseif (is_link($path) && is_dir($path) && $isWindows) {
2114  $OK = @‪rmdir($path);
2115  } else {
2116  // If $path is a file, simply remove it
2117  $OK = @unlink($path);
2118  }
2119  clearstatcache();
2120  } elseif (is_link($path)) {
2121  $OK = @unlink($path);
2122  if (!$OK && $isWindows) {
2123  // Try to delete dead folder links on Windows systems
2124  $OK = @‪rmdir($path);
2125  }
2126  clearstatcache();
2127  }
2128  return $OK;
2129  }
2130 
2142  public static function ‪flushDirectory($directory, $keepOriginalDirectory = false, $flushOpcodeCache = false)
2143  {
2144  trigger_error('GeneralUtility::flushDirectory() will be removed in TYPO3 v11.0. This is a specific logic needed for the caching framework, and should be implemented where needed directly.', E_USER_DEPRECATED);
2145  $result = false;
2146 
2147  if (is_link($directory)) {
2148  // Avoid attempting to rename the symlink see #87367
2149  $directory = realpath($directory);
2150  }
2151 
2152  if (is_dir($directory)) {
2153  $temporaryDirectory = rtrim($directory, '/') . '.' . ‪StringUtility::getUniqueId('remove');
2154  if (rename($directory, $temporaryDirectory)) {
2155  if ($flushOpcodeCache) {
2156  self::makeInstance(OpcodeCacheService::class)->clearAllActive($directory);
2157  }
2158  if ($keepOriginalDirectory) {
2159  static::mkdir($directory);
2160  }
2161  clearstatcache();
2162  $result = static::rmdir($temporaryDirectory, true);
2163  }
2164  }
2165 
2166  return $result;
2167  }
2168 
2177  public static function ‪get_dirs($path)
2178  {
2179  $dirs = null;
2180  if ($path) {
2181  if (is_dir($path)) {
2182  ‪$dir = scandir($path);
2183  $dirs = [];
2184  foreach (‪$dir as $entry) {
2185  if (is_dir($path . '/' . $entry) && $entry !== '..' && $entry !== '.') {
2186  $dirs[] = $entry;
2187  }
2188  }
2189  } else {
2190  $dirs = 'error';
2191  }
2192  }
2193  return $dirs;
2194  }
2195 
2208  public static function getFilesInDir($path, $extensionList = '', $prependPath = false, $order = '', $excludePattern = '')
2209  {
2210  $excludePattern = (string)$excludePattern;
2211  $path = rtrim($path, '/');
2212  if (!@is_dir($path)) {
2213  return [];
2214  }
2215 
2216  $rawFileList = scandir($path);
2217  if ($rawFileList === false) {
2218  return 'error opening path: "' . $path . '"';
2219  }
2220 
2221  $pathPrefix = $path . '/';
2222  $allowedFileExtensionArray = ‪self::trimExplode(',', $extensionList);
2223  $extensionList = ',' . str_replace(' ', '', $extensionList) . ',';
2224  $files = [];
2225  foreach ($rawFileList as $entry) {
2226  $completePathToEntry = $pathPrefix . $entry;
2227  if (!@is_file($completePathToEntry)) {
2228  continue;
2229  }
2230 
2231  foreach ($allowedFileExtensionArray as $allowedFileExtension) {
2232  if (
2233  ($extensionList === ',,' || stripos($extensionList, ',' . substr($entry, strlen($allowedFileExtension) * -1, strlen($allowedFileExtension)) . ',') !== false)
2234  && ($excludePattern === '' || !preg_match('/^' . $excludePattern . '$/', $entry))
2235  ) {
2236  if ($order !== 'mtime') {
2237  $files[] = $entry;
2238  } else {
2239  // Store the value in the key so we can do a fast asort later.
2240  $files[$entry] = filemtime($completePathToEntry);
2241  }
2242  }
2243  }
2244  }
2245 
2246  $valueName = 'value';
2247  if ($order === 'mtime') {
2248  asort($files);
2249  $valueName = 'key';
2250  }
2251 
2252  $valuePathPrefix = $prependPath ? $pathPrefix : '';
2253  $foundFiles = [];
2254  foreach ($files as $key => $value) {
2255  // Don't change this ever - extensions may depend on the fact that the hash is an md5 of the path! (import/export extension)
2256  $foundFiles[md5($pathPrefix . ${$valueName})] = $valuePathPrefix . ${$valueName};
2257  }
2258 
2259  return $foundFiles;
2260  }
2261 
2273  public static function getAllFilesAndFoldersInPath(array $fileArr, $path, $extList = '', $regDirs = false, $recursivityLevels = 99, $excludePattern = '')
2274  {
2275  if ($regDirs) {
2276  $fileArr[md5($path)] = $path;
2277  }
2278  $fileArr = array_merge($fileArr, (array)self::getFilesInDir($path, $extList, true, '', $excludePattern));
2279  $dirs = ‪self::get_dirs($path);
2280  if ($recursivityLevels > 0 && is_array($dirs)) {
2281  foreach ($dirs as $subdirs) {
2282  if ((string)$subdirs !== '' && ($excludePattern === '' || !preg_match('/^' . $excludePattern . '$/', $subdirs))) {
2283  $fileArr = self::getAllFilesAndFoldersInPath($fileArr, $path . $subdirs . '/', $extList, $regDirs, $recursivityLevels - 1, $excludePattern);
2284  }
2285  }
2286  }
2287  return $fileArr;
2288  }
2289 
2297  public static function removePrefixPathFromList(array $fileArr, $prefixToRemove)
2298  {
2299  foreach ($fileArr as $k => &$absFileRef) {
2300  if (self::isFirstPartOfStr($absFileRef, $prefixToRemove)) {
2301  $absFileRef = substr($absFileRef, strlen($prefixToRemove));
2302  } else {
2303  return 'ERROR: One or more of the files was NOT prefixed with the prefix-path!';
2304  }
2305  }
2306  unset($absFileRef);
2307  return $fileArr;
2308  }
2309 
2316  public static function fixWindowsFilePath($theFile)
2317  {
2318  return str_replace(['\\', '//'], '/', $theFile);
2319  }
2320 
2328  public static function resolveBackPath($pathStr)
2329  {
2330  if (strpos($pathStr, '..') === false) {
2331  return $pathStr;
2332  }
2333  $parts = explode('/', $pathStr);
2334  ‪$output = [];
2335  $c = 0;
2336  foreach ($parts as $part) {
2337  if ($part === '..') {
2338  if ($c) {
2339  array_pop(‪$output);
2340  --$c;
2341  } else {
2342  ‪$output[] = $part;
2343  }
2344  } else {
2345  ++$c;
2346  ‪$output[] = $part;
2347  }
2348  }
2349  return implode('/', ‪$output);
2350  }
2351 
2361  public static function locationHeaderUrl($path)
2362  {
2363  if (strpos($path, '//') === 0) {
2364  return $path;
2365  }
2366 
2367  // relative to HOST
2368  if (strpos($path, '/') === 0) {
2369  return self::getIndpEnv('TYPO3_REQUEST_HOST') . $path;
2370  }
2371 
2372  $urlComponents = parse_url($path);
2373  if (!($urlComponents['scheme'] ?? false)) {
2374  // No scheme either
2375  return self::getIndpEnv('TYPO3_REQUEST_DIR') . $path;
2376  }
2377 
2378  return $path;
2379  }
2380 
2388  public static function getMaxUploadFileSize()
2389  {
2390  $uploadMaxFilesize = (string)ini_get('upload_max_filesize');
2391  $postMaxSize = (string)ini_get('post_max_size');
2392  // Check for PHP restrictions of the maximum size of one of the $_FILES
2393  $phpUploadLimit = self::getBytesFromSizeMeasurement($uploadMaxFilesize);
2394  // Check for PHP restrictions of the maximum $_POST size
2395  $phpPostLimit = self::getBytesFromSizeMeasurement($postMaxSize);
2396  // If the total amount of post data is smaller (!) than the upload_max_filesize directive,
2397  // then this is the real limit in PHP
2398  $phpUploadLimit = $phpPostLimit > 0 && $phpPostLimit < $phpUploadLimit ? $phpPostLimit : $phpUploadLimit;
2399  return floor($phpUploadLimit) / 1024;
2400  }
2401 
2408  public static function getBytesFromSizeMeasurement($measurement)
2409  {
2410  $bytes = (float)$measurement;
2411  if (stripos($measurement, 'G')) {
2412  $bytes *= 1024 * 1024 * 1024;
2413  } elseif (stripos($measurement, 'M')) {
2414  $bytes *= 1024 * 1024;
2415  } elseif (stripos($measurement, 'K')) {
2416  $bytes *= 1024;
2417  }
2418  return (int)$bytes;
2419  }
2420 
2437  public static function createVersionNumberedFilename($file)
2438  {
2439  $isFrontend = TYPO3_MODE === 'FE';
2440  $lookupFile = explode('?', $file);
2441  $path = $lookupFile[0];
2442 
2443  // @todo: in v12 this should be resolved by using Environment::getPublicPath() once
2444  if ($isFrontend) {
2445  // Frontend should still allow /static/myfile.css - see #98106
2446  // This should happen regardless of the incoming path is absolute or not
2447  $path = self::resolveBackPath(self::dirname(‪Environment::getCurrentScript()) . '/' . $path);
2448  } elseif (!‪PathUtility::isAbsolutePath($path)) {
2449  // Backend and non-absolute path
2450  $path = self::resolveBackPath(self::dirname(‪Environment::getCurrentScript()) . '/' . $path);
2451  }
2452 
2453  $doNothing = false;
2454  if ($isFrontend) {
2455  $mode = strtolower(‪$GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['versionNumberInFilename']);
2456  if ($mode === 'embed') {
2457  $mode = true;
2458  } else {
2459  if ($mode === 'querystring') {
2460  $mode = false;
2461  } else {
2462  $doNothing = true;
2463  }
2464  }
2465  } else {
2466  $mode = ‪$GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['versionNumberInFilename'];
2467  }
2468  try {
2469  $fileExists = file_exists($path);
2470  } catch (\Throwable $e) {
2471  $fileExists = false;
2472  }
2473  if ($doNothing || !$fileExists) {
2474  // File not found, return filename unaltered
2475  $fullName = $file;
2476  } else {
2477  if (!$mode) {
2478  // If use of .htaccess rule is not configured,
2479  // we use the default query-string method
2480  if (!empty($lookupFile[1])) {
2481  $separator = '&';
2482  } else {
2483  $separator = '?';
2484  }
2485  $fullName = $file . $separator . filemtime($path);
2486  } else {
2487  // Change the filename
2488  $name = explode('.', $lookupFile[0]);
2489  $extension = array_pop($name);
2490  array_push($name, filemtime($path), $extension);
2491  $fullName = implode('.', $name);
2492  // Append potential query string
2493  $fullName .= !empty($lookupFile[1]) ? '?' . $lookupFile[1] : '';
2494  }
2495  }
2496  return $fullName;
2497  }
2498 
2506  public static function writeJavaScriptContentToTemporaryFile(string $content)
2507  {
2508  $script = 'typo3temp/assets/js/' . GeneralUtility::shortMD5($content) . '.js';
2509  if (!@is_file(‪Environment::getPublicPath() . '/' . $script)) {
2511  }
2512  return $script;
2513  }
2514 
2522  public static function writeStyleSheetContentToTemporaryFile(string $content)
2523  {
2524  $script = 'typo3temp/assets/css/' . self::shortMD5($content) . '.css';
2525  if (!@is_file(‪Environment::getPublicPath() . '/' . $script)) {
2527  }
2528  return $script;
2529  }
2530 
2531  /*************************
2532  *
2533  * SYSTEM INFORMATION
2534  *
2535  *************************/
2536 
2545  public static function linkThisScript(array $getParams = [])
2546  {
2547  $parts = self::getIndpEnv('SCRIPT_NAME');
2548  $params = self::_GET();
2549  foreach ($getParams as $key => $value) {
2550  if ($value !== '') {
2551  $params[$key] = $value;
2552  } else {
2553  unset($params[$key]);
2554  }
2555  }
2556  $pString = ‪self::implodeArrayForUrl('', $params);
2557  return $pString ? $parts . '?' . ltrim($pString, '&') : $parts;
2558  }
2559 
2569  public static function linkThisUrl($url, array $getParams = [])
2570  {
2571  trigger_error('GeneralUtility::linkThisUrl() will be removed in TYPO3 v11.0. Use PSR-7 URI objects instead.', E_USER_DEPRECATED);
2572  $parts = parse_url($url);
2573  $getP = [];
2574  if ($parts['query']) {
2575  parse_str($parts['query'], $getP);
2576  }
2578  $uP = explode('?', $url);
2579  $params = ‪self::implodeArrayForUrl('', $getP);
2580  $outurl = $uP[0] . ($params ? '?' . substr($params, 1) : '');
2581  return $outurl;
2582  }
2583 
2591  public static function setIndpEnv($envName, $value)
2592  {
2593  self::$indpEnvCache[$envName] = $value;
2594  }
2595 
2604  public static function getIndpEnv($getEnvName)
2605  {
2606  if (array_key_exists($getEnvName, self::$indpEnvCache)) {
2607  return self::$indpEnvCache[$getEnvName];
2608  }
2609 
2610  /*
2611  Conventions:
2612  output from parse_url():
2613  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
2614  [scheme] => 'http'
2615  [user] => 'username'
2616  [pass] => 'password'
2617  [host] => '192.168.1.4'
2618  [port] => '8080'
2619  [path] => '/typo3/32/temp/phpcheck/index.php/arg1/arg2/arg3/'
2620  [query] => 'arg1,arg2,arg3&p1=parameter1&p2[key]=value'
2621  [fragment] => 'link1'Further definition: [path_script] = '/typo3/32/temp/phpcheck/index.php'
2622  [path_dir] = '/typo3/32/temp/phpcheck/'
2623  [path_info] = '/arg1/arg2/arg3/'
2624  [path] = [path_script/path_dir][path_info]Keys supported:URI______:
2625  REQUEST_URI = [path]?[query] = /typo3/32/temp/phpcheck/index.php/arg1/arg2/arg3/?arg1,arg2,arg3&p1=parameter1&p2[key]=value
2626  HTTP_HOST = [host][:[port]] = 192.168.1.4:8080
2627  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')!
2628  PATH_INFO = [path_info] = /arg1/arg2/arg3/
2629  QUERY_STRING = [query] = arg1,arg2,arg3&p1=parameter1&p2[key]=value
2630  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
2631  (Notice: NO username/password + NO fragment)CLIENT____:
2632  REMOTE_ADDR = (client IP)
2633  REMOTE_HOST = (client host)
2634  HTTP_USER_AGENT = (client user agent)
2635  HTTP_ACCEPT_LANGUAGE = (client accept language)SERVER____:
2636  SCRIPT_FILENAME = Absolute filename of script (Differs between windows/unix). On windows 'C:\\some\\path\\' will be converted to 'C:/some/path/'Special extras:
2637  TYPO3_HOST_ONLY = [host] = 192.168.1.4
2638  TYPO3_PORT = [port] = 8080 (blank if 80, taken from host value)
2639  TYPO3_REQUEST_HOST = [scheme]://[host][:[port]]
2640  TYPO3_REQUEST_URL = [scheme]://[host][:[port]][path]?[query] (scheme will by default be "http" until we can detect something different)
2641  TYPO3_REQUEST_SCRIPT = [scheme]://[host][:[port]][path_script]
2642  TYPO3_REQUEST_DIR = [scheme]://[host][:[port]][path_dir]
2643  TYPO3_SITE_URL = [scheme]://[host][:[port]][path_dir] of the TYPO3 website frontend
2644  TYPO3_SITE_PATH = [path_dir] of the TYPO3 website frontend
2645  TYPO3_SITE_SCRIPT = [script / Speaking URL] of the TYPO3 website
2646  TYPO3_DOCUMENT_ROOT = Absolute path of root of documents: TYPO3_DOCUMENT_ROOT.SCRIPT_NAME = SCRIPT_FILENAME (typically)
2647  TYPO3_SSL = Returns TRUE if this session uses SSL/TLS (https)
2648  TYPO3_PROXY = Returns TRUE if this session runs over a well known proxyNotice: [fragment] is apparently NEVER available to the script!Testing suggestions:
2649  - Output all the values.
2650  - 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
2651  - ALSO TRY the script from the ROOT of a site (like 'http://www.mytest.com/' and not 'http://www.mytest.com/test/' !!)
2652  */
2653  $retVal = '';
2654  switch ((string)$getEnvName) {
2655  case 'SCRIPT_NAME':
2656  $retVal = $_SERVER['SCRIPT_NAME'] ?? '';
2657  // Add a prefix if TYPO3 is behind a proxy: ext-domain.com => int-server.com/prefix
2658  if (self::cmpIP($_SERVER['REMOTE_ADDR'] ?? '', ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyIP'] ?? '')) {
2659  if (self::getIndpEnv('TYPO3_SSL') && ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyPrefixSSL']) {
2660  $retVal = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyPrefixSSL'] . $retVal;
2661  } elseif (‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyPrefix']) {
2662  $retVal = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyPrefix'] . $retVal;
2663  }
2664  }
2665  $retVal = self::encodeFileSystemPathComponentForUrlPath($retVal);
2666  break;
2667  case 'SCRIPT_FILENAME':
2669  break;
2670  case 'REQUEST_URI':
2671  // Typical application of REQUEST_URI is return urls, forms submitting to itself etc. Example: returnUrl='.rawurlencode(\TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('REQUEST_URI'))
2672  if (!empty(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['requestURIvar'])) {
2673  // This is for URL rewriters that store the original URI in a server variable (eg ISAPI_Rewriter for IIS: HTTP_X_REWRITE_URL)
2674  [$v, $n] = explode('|', ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['requestURIvar']);
2675  $retVal = ‪$GLOBALS[$v][$n];
2676  } elseif (empty($_SERVER['REQUEST_URI'])) {
2677  // This is for ISS/CGI which does not have the REQUEST_URI available.
2678  $retVal = '/' . ltrim(self::getIndpEnv('SCRIPT_NAME'), '/') . (!empty($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : '');
2679  } else {
2680  $retVal = '/' . ltrim($_SERVER['REQUEST_URI'], '/');
2681  }
2682  // Add a prefix if TYPO3 is behind a proxy: ext-domain.com => int-server.com/prefix
2683  if (isset($_SERVER['REMOTE_ADDR'], ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyIP'])
2684  && self::cmpIP($_SERVER['REMOTE_ADDR'], ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyIP'])
2685  ) {
2686  if (self::getIndpEnv('TYPO3_SSL') && ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyPrefixSSL']) {
2687  $retVal = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyPrefixSSL'] . $retVal;
2688  } elseif (‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyPrefix']) {
2689  $retVal = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyPrefix'] . $retVal;
2690  }
2691  }
2692  break;
2693  case 'PATH_INFO':
2694  $retVal = $_SERVER['PATH_INFO'] ?? '';
2695  break;
2696  case 'TYPO3_REV_PROXY':
2697  $retVal = self::cmpIP($_SERVER['REMOTE_ADDR'], ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyIP']);
2698  break;
2699  case 'REMOTE_ADDR':
2700  $retVal = $_SERVER['REMOTE_ADDR'] ?? null;
2701  if (self::cmpIP($retVal, ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyIP'] ?? '')) {
2702  $ip = ‪self::trimExplode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
2703  // Choose which IP in list to use
2704  if (!empty($ip)) {
2705  switch (‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyHeaderMultiValue']) {
2706  case 'last':
2707  $ip = array_pop($ip);
2708  break;
2709  case 'first':
2710  $ip = array_shift($ip);
2711  break;
2712  case 'none':
2713 
2714  default:
2715  $ip = '';
2716  }
2717  }
2718  if (self::validIP((string)$ip)) {
2719  $retVal = $ip;
2720  }
2721  }
2722  break;
2723  case 'HTTP_HOST':
2724  // if it is not set we're most likely on the cli
2725  $retVal = $_SERVER['HTTP_HOST'] ?? null;
2726  if (isset($_SERVER['REMOTE_ADDR']) && static::cmpIP($_SERVER['REMOTE_ADDR'], ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyIP'])) {
2727  $host = ‪self::trimExplode(',', $_SERVER['HTTP_X_FORWARDED_HOST']);
2728  // Choose which host in list to use
2729  if (!empty($host)) {
2730  switch (‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyHeaderMultiValue']) {
2731  case 'last':
2732  $host = array_pop($host);
2733  break;
2734  case 'first':
2735  $host = array_shift($host);
2736  break;
2737  case 'none':
2738 
2739  default:
2740  $host = '';
2741  }
2742  }
2743  if ($host) {
2744  $retVal = $host;
2745  }
2746  }
2747  if (!static::isAllowedHostHeaderValue($retVal)) {
2748  throw new \UnexpectedValueException(
2749  '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.',
2750  1396795884
2751  );
2752  }
2753  break;
2754  case 'HTTP_REFERER':
2755 
2756  case 'HTTP_USER_AGENT':
2757 
2758  case 'HTTP_ACCEPT_ENCODING':
2759 
2760  case 'HTTP_ACCEPT_LANGUAGE':
2761 
2762  case 'REMOTE_HOST':
2763 
2764  case 'QUERY_STRING':
2765  $retVal = $_SERVER[$getEnvName] ?? '';
2766  break;
2767  case 'TYPO3_DOCUMENT_ROOT':
2768  // Get the web root (it is not the root of the TYPO3 installation)
2769  // The absolute path of the script can be calculated with TYPO3_DOCUMENT_ROOT + SCRIPT_FILENAME
2770  // 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.
2771  // Therefore the DOCUMENT_ROOT is now always calculated as the SCRIPT_FILENAME minus the end part shared with SCRIPT_NAME.
2772  $SFN = self::getIndpEnv('SCRIPT_FILENAME');
2773  // Use rawurldecode to reverse the result of self::encodeFileSystemPathComponentForUrlPath()
2774  // which has been applied to getIndpEnv(SCRIPT_NAME) for web URI usage.
2775  // We compare with a file system path (SCRIPT_FILENAME) in here and therefore need to undo the encoding.
2776  $SN_A = array_map('rawurldecode', explode('/', strrev(self::getIndpEnv('SCRIPT_NAME'))));
2777  $SFN_A = explode('/', strrev($SFN));
2778  $acc = [];
2779  foreach ($SN_A as $kk => $vv) {
2780  if ((string)$SFN_A[$kk] === (string)$vv) {
2781  $acc[] = $vv;
2782  } else {
2783  break;
2784  }
2785  }
2786  $commonEnd = strrev(implode('/', $acc));
2787  if ((string)$commonEnd !== '') {
2788  $retVal = substr($SFN, 0, -(strlen($commonEnd) + 1));
2789  }
2790  break;
2791  case 'TYPO3_HOST_ONLY':
2792  $httpHost = self::getIndpEnv('HTTP_HOST');
2793  $httpHostBracketPosition = strpos($httpHost, ']');
2794  $httpHostParts = explode(':', $httpHost);
2795  $retVal = $httpHostBracketPosition !== false ? substr($httpHost, 0, $httpHostBracketPosition + 1) : array_shift($httpHostParts);
2796  break;
2797  case 'TYPO3_PORT':
2798  $httpHost = self::getIndpEnv('HTTP_HOST');
2799  $httpHostOnly = self::getIndpEnv('TYPO3_HOST_ONLY');
2800  $retVal = strlen($httpHost) > strlen($httpHostOnly) ? substr($httpHost, strlen($httpHostOnly) + 1) : '';
2801  break;
2802  case 'TYPO3_REQUEST_HOST':
2803  $retVal = (self::getIndpEnv('TYPO3_SSL') ? 'https://' : 'http://') . self::getIndpEnv('HTTP_HOST');
2804  break;
2805  case 'TYPO3_REQUEST_URL':
2806  $retVal = self::getIndpEnv('TYPO3_REQUEST_HOST') . self::getIndpEnv('REQUEST_URI');
2807  break;
2808  case 'TYPO3_REQUEST_SCRIPT':
2809  $retVal = self::getIndpEnv('TYPO3_REQUEST_HOST') . self::getIndpEnv('SCRIPT_NAME');
2810  break;
2811  case 'TYPO3_REQUEST_DIR':
2812  $retVal = self::getIndpEnv('TYPO3_REQUEST_HOST') . self::dirname(self::getIndpEnv('SCRIPT_NAME')) . '/';
2813  break;
2814  case 'TYPO3_SITE_URL':
2817  $url = self::getIndpEnv('TYPO3_REQUEST_DIR');
2818  $siteUrl = substr($url, 0, -strlen($lPath));
2819  if (substr($siteUrl, -1) !== '/') {
2820  $siteUrl .= '/';
2821  }
2822  $retVal = $siteUrl;
2823  }
2824  break;
2825  case 'TYPO3_SITE_PATH':
2826  $retVal = substr(self::getIndpEnv('TYPO3_SITE_URL'), strlen(self::getIndpEnv('TYPO3_REQUEST_HOST')));
2827  break;
2828  case 'TYPO3_SITE_SCRIPT':
2829  $retVal = substr(self::getIndpEnv('TYPO3_REQUEST_URL'), strlen(self::getIndpEnv('TYPO3_SITE_URL')));
2830  break;
2831  case 'TYPO3_SSL':
2832  $proxySSL = trim(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxySSL'] ?? null);
2833  if ($proxySSL === '*') {
2834  $proxySSL = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyIP'];
2835  }
2836  if (self::cmpIP($_SERVER['REMOTE_ADDR'] ?? '', $proxySSL)) {
2837  $retVal = true;
2838  } else {
2839  $retVal = self::webserverUsesHttps();
2840  }
2841  break;
2842  case '_ARRAY':
2843  $out = [];
2844  // Here, list ALL possible keys to this function for debug display.
2845  $envTestVars = [
2846  'HTTP_HOST',
2847  'TYPO3_HOST_ONLY',
2848  'TYPO3_PORT',
2849  'PATH_INFO',
2850  'QUERY_STRING',
2851  'REQUEST_URI',
2852  'HTTP_REFERER',
2853  'TYPO3_REQUEST_HOST',
2854  'TYPO3_REQUEST_URL',
2855  'TYPO3_REQUEST_SCRIPT',
2856  'TYPO3_REQUEST_DIR',
2857  'TYPO3_SITE_URL',
2858  'TYPO3_SITE_SCRIPT',
2859  'TYPO3_SSL',
2860  'TYPO3_REV_PROXY',
2861  'SCRIPT_NAME',
2862  'TYPO3_DOCUMENT_ROOT',
2863  'SCRIPT_FILENAME',
2864  'REMOTE_ADDR',
2865  'REMOTE_HOST',
2866  'HTTP_USER_AGENT',
2867  'HTTP_ACCEPT_LANGUAGE'
2868  ];
2869  foreach ($envTestVars as $v) {
2870  $out[$v] = self::getIndpEnv($v);
2871  }
2872  reset($out);
2873  $retVal = $out;
2874  break;
2875  }
2876  self::$indpEnvCache[$getEnvName] = $retVal;
2877  return $retVal;
2878  }
2879 
2888  public static function isAllowedHostHeaderValue($hostHeaderValue)
2889  {
2890  if (static::$allowHostHeaderValue === true) {
2891  return true;
2892  }
2893 
2894  if (static::isInternalRequestType()) {
2895  return static::$allowHostHeaderValue = true;
2896  }
2897 
2898  // Deny the value if trusted host patterns is empty, which means we are early in the bootstrap
2899  if (empty(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'])) {
2900  return false;
2901  }
2902 
2903  if (‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] === self::ENV_TRUSTED_HOSTS_PATTERN_ALLOW_ALL) {
2904  static::$allowHostHeaderValue = true;
2905  } else {
2906  static::$allowHostHeaderValue = static::hostHeaderValueMatchesTrustedHostsPattern($hostHeaderValue);
2907  }
2908 
2909  return static::$allowHostHeaderValue;
2910  }
2911 
2919  public static function hostHeaderValueMatchesTrustedHostsPattern($hostHeaderValue)
2920  {
2921  if (‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] === self::ENV_TRUSTED_HOSTS_PATTERN_SERVER_NAME) {
2922  $host = strtolower($hostHeaderValue);
2923  // Default port to be verified if HTTP_HOST does not contain explicit port information.
2924  // Deriving from raw/local webserver HTTPS information (not taking possible proxy configurations into account)
2925  // as we compare against the raw/local server information (SERVER_PORT).
2926  $port = self::webserverUsesHttps() ? '443' : '80';
2927 
2928  $parsedHostValue = parse_url('http://' . $host);
2929  if (isset($parsedHostValue['port'])) {
2930  $host = $parsedHostValue['host'];
2931  $port = (string)$parsedHostValue['port'];
2932  }
2933 
2934  // Allow values that equal the server name
2935  // Note that this is only secure if name base virtual host are configured correctly in the webserver
2936  $hostMatch = $host === strtolower($_SERVER['SERVER_NAME']) && $port === $_SERVER['SERVER_PORT'];
2937  } else {
2938  // In case name based virtual hosts are not possible, we allow setting a trusted host pattern
2939  // See https://typo3.org/teams/security/security-bulletins/typo3-core/typo3-core-sa-2014-001/ for further details
2940  $hostMatch = (bool)preg_match('/^' . ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] . '$/i', $hostHeaderValue);
2941  }
2942 
2943  return $hostMatch;
2944  }
2945 
2956  protected static function webserverUsesHttps()
2957  {
2958  if (!empty($_SERVER['SSL_SESSION_ID'])) {
2959  return true;
2960  }
2961 
2962  // https://secure.php.net/manual/en/reserved.variables.server.php
2963  // "Set to a non-empty value if the script was queried through the HTTPS protocol."
2964  return !empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off';
2965  }
2966 
2967  protected static function encodeFileSystemPathComponentForUrlPath(string $path): string
2968  {
2969  return implode('/', array_map('rawurlencode', explode('/', $path)));
2970  }
2971 
2982  protected static function isInternalRequestType()
2983  {
2984  return ‪Environment::isCli() || !defined('TYPO3_REQUESTTYPE') || (defined('TYPO3_REQUESTTYPE') && TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_INSTALL);
2985  }
2986 
2993  public static function milliseconds()
2994  {
2995  trigger_error('GeneralUtility::milliseconds() will be removed in TYPO3 v11.0. Use the native PHP functions round(microtime(true) * 1000) instead.', E_USER_DEPRECATED);
2996  return round(microtime(true) * 1000);
2997  }
2998 
2999  /*************************
3000  *
3001  * TYPO3 SPECIFIC FUNCTIONS
3002  *
3003  *************************/
3013  public static function getFileAbsFileName($filename)
3014  {
3015  if ((string)$filename === '') {
3016  return '';
3017  }
3018  // Extension
3019  if (strpos($filename, 'EXT:') === 0) {
3020  [$extKey, $local] = explode('/', substr($filename, 4), 2);
3021  $filename = '';
3022  if ((string)$extKey !== '' && ‪ExtensionManagementUtility::isLoaded($extKey) && (string)$local !== '') {
3023  $filename = ‪ExtensionManagementUtility::extPath($extKey) . $local;
3024  }
3025  } elseif (!static::isAbsPath($filename)) {
3026  // is relative. Prepended with the public web folder
3027  $filename = ‪Environment::getPublicPath() . '/' . $filename;
3028  } elseif (!(
3029  static::isFirstPartOfStr($filename, ‪Environment::getProjectPath())
3030  || static::isFirstPartOfStr($filename, ‪Environment::getPublicPath())
3031  )) {
3032  // absolute, but set to blank if not allowed
3033  $filename = '';
3034  }
3035  if ((string)$filename !== '' && static::validPathStr($filename)) {
3036  // checks backpath.
3037  return $filename;
3038  }
3039  return '';
3040  }
3041 
3053  public static function validPathStr($theFile)
3054  {
3055  return strpos($theFile, '//') === false && strpos($theFile, '\\') === false
3056  && preg_match('#(?:^\\.\\.|/\\.\\./|[[:cntrl:]])#u', $theFile) === 0;
3057  }
3058 
3065  public static function isAbsPath($path)
3066  {
3067  if (substr($path, 0, 6) === 'vfs://') {
3068  return true;
3069  }
3070  return isset($path[0]) && $path[0] === '/' || ‪Environment::isWindows() && (strpos($path, ':/') === 1 || strpos($path, ':\\') === 1);
3071  }
3072 
3079  public static function isAllowedAbsPath($path)
3080  {
3081  if (substr($path, 0, 6) === 'vfs://') {
3082  return true;
3083  }
3084  $lockRootPath = ‪$GLOBALS['TYPO3_CONF_VARS']['BE']['lockRootPath'];
3085  return static::isAbsPath($path) && static::validPathStr($path)
3086  && (
3087  static::isFirstPartOfStr($path, ‪Environment::getProjectPath())
3088  || static::isFirstPartOfStr($path, ‪Environment::getPublicPath())
3089  || $lockRootPath && static::isFirstPartOfStr($path, $lockRootPath)
3090  );
3091  }
3092 
3103  public static function verifyFilenameAgainstDenyPattern($filename)
3104  {
3105  trigger_error('GeneralUtility::verifyFilenameAgainstDenyPattern() will be removed in TYPO3 v11.0. Use FileNameValidator->isValid($filename) instead.', E_USER_DEPRECATED);
3106  return self::makeInstance(FileNameValidator::class)->isValid((string)$filename);
3107  }
3108 
3115  public static function copyDirectory($source, $destination)
3116  {
3117  if (strpos($source, ‪Environment::getProjectPath() . '/') === false) {
3118  $source = ‪Environment::getPublicPath() . '/' . $source;
3119  }
3120  if (strpos($destination, ‪Environment::getProjectPath() . '/') === false) {
3121  $destination = ‪Environment::getPublicPath() . '/' . $destination;
3122  }
3123  if (static::isAllowedAbsPath($source) && static::isAllowedAbsPath($destination)) {
3124  static::mkdir_deep($destination);
3125  $iterator = new \RecursiveIteratorIterator(
3126  new \RecursiveDirectoryIterator($source, \RecursiveDirectoryIterator::SKIP_DOTS),
3127  \RecursiveIteratorIterator::SELF_FIRST
3128  );
3130  foreach ($iterator as $item) {
3131  $target = $destination . '/' . static::fixWindowsFilePath($iterator->getSubPathName());
3132  if ($item->isDir()) {
3133  static::mkdir($target);
3134  } else {
3135  static::upload_copy_move(static::fixWindowsFilePath($item->getPathname()), $target);
3136  }
3137  }
3138  }
3139  }
3140 
3151  public static function sanitizeLocalUrl($url = '')
3152  {
3153  $sanitizedUrl = '';
3154  if (!empty($url)) {
3155  $decodedUrl = rawurldecode($url);
3156  $parsedUrl = parse_url($decodedUrl);
3157  $testAbsoluteUrl = self::resolveBackPath($decodedUrl);
3158  $testRelativeUrl = self::resolveBackPath(self::dirname(self::getIndpEnv('SCRIPT_NAME')) . '/' . $decodedUrl);
3159  // Pass if URL is on the current host:
3160  if (self::isValidUrl($decodedUrl)) {
3161  if (self::isOnCurrentHost($decodedUrl) && strpos($decodedUrl, self::getIndpEnv('TYPO3_SITE_URL')) === 0) {
3162  $sanitizedUrl = $url;
3163  }
3164  } elseif (self::isAbsPath($decodedUrl) && self::isAllowedAbsPath($decodedUrl)) {
3165  $sanitizedUrl = $url;
3166  } elseif (strpos($testAbsoluteUrl, self::getIndpEnv('TYPO3_SITE_PATH')) === 0 && $decodedUrl[0] === '/' &&
3167  substr($decodedUrl, 0, 2) !== '//'
3168  ) {
3169  $sanitizedUrl = $url;
3170  } elseif (empty($parsedUrl['scheme']) && strpos($testRelativeUrl, self::getIndpEnv('TYPO3_SITE_PATH')) === 0
3171  && $decodedUrl[0] !== '/' && strpbrk($decodedUrl, '*:|"<>') === false && strpos($decodedUrl, '\\\\') === false
3172  ) {
3173  $sanitizedUrl = $url;
3174  }
3175  }
3176  if (!empty($url) && empty($sanitizedUrl)) {
3177  static::getLogger()->notice('The URL "' . $url . '" is not considered to be local and was denied.');
3178  }
3179  return $sanitizedUrl;
3180  }
3181 
3190  public static function upload_copy_move($source, $destination)
3191  {
3192  if (is_array(‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][\‪TYPO3\CMS\Core\Utility\GeneralUtility::class]['moveUploadedFile'] ?? null)) {
3193  $params = ['source' => $source, 'destination' => $destination, 'method' => 'upload_copy_move'];
3194  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][\‪TYPO3\CMS\Core\Utility\GeneralUtility::class]['moveUploadedFile'] as $hookMethod) {
3195  $fakeThis = null;
3196  self::callUserFunction($hookMethod, $params, $fakeThis);
3197  }
3198  }
3199 
3200  $result = false;
3201  if (is_uploaded_file($source)) {
3202  // Return the value of move_uploaded_file, and if FALSE the temporary $source is still
3203  // around so the user can use unlink to delete it:
3204  $result = move_uploaded_file($source, $destination);
3205  } else {
3206  @copy($source, $destination);
3207  }
3208  // Change the permissions of the file
3209  ‪self::fixPermissions($destination);
3210  // If here the file is copied and the temporary $source is still around,
3211  // so when returning FALSE the user can try unlink to delete the $source
3212  return $result;
3213  }
3214 
3225  public static function upload_to_tempfile($uploadedFileName)
3226  {
3227  if (is_uploaded_file($uploadedFileName)) {
3228  $tempFile = self::tempnam('upload_temp_');
3229  if (is_array(‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][\‪TYPO3\CMS\Core\Utility\GeneralUtility::class]['moveUploadedFile'] ?? null)) {
3230  $params = ['source' => $uploadedFileName, 'destination' => $tempFile, 'method' => 'upload_to_tempfile'];
3231  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][\‪TYPO3\CMS\Core\Utility\GeneralUtility::class]['moveUploadedFile'] as $hookMethod) {
3232  $fakeThis = null;
3233  self::callUserFunction($hookMethod, $params, $fakeThis);
3234  }
3235  }
3236 
3237  move_uploaded_file($uploadedFileName, $tempFile);
3238  return @is_file($tempFile) ? $tempFile : '';
3239  }
3240 
3241  return '';
3242  }
3243 
3254  public static function unlink_tempfile($uploadedTempFileName)
3255  {
3256  if ($uploadedTempFileName) {
3257  $uploadedTempFileName = self::fixWindowsFilePath($uploadedTempFileName);
3258  if (
3259  self::validPathStr($uploadedTempFileName)
3260  && (
3261  self::isFirstPartOfStr($uploadedTempFileName, ‪Environment::getPublicPath() . '/typo3temp/')
3262  || self::isFirstPartOfStr($uploadedTempFileName, ‪Environment::getVarPath() . '/')
3263  )
3264  && @is_file($uploadedTempFileName)
3265  ) {
3266  if (unlink($uploadedTempFileName)) {
3267  return true;
3268  }
3269  }
3270  }
3271 
3272  return null;
3273  }
3274 
3286  public static function tempnam($filePrefix, $fileSuffix = '')
3287  {
3288  $temporaryPath = ‪Environment::getVarPath() . '/transient/';
3289  if (!is_dir($temporaryPath)) {
3290  ‪self::mkdir_deep($temporaryPath);
3291  }
3292  if ($fileSuffix === '') {
3293  $path = (string)tempnam($temporaryPath, $filePrefix);
3294  $tempFileName = $temporaryPath . ‪PathUtility::basename($path);
3295  } else {
3296  do {
3297  $tempFileName = $temporaryPath . $filePrefix . random_int(1, PHP_INT_MAX) . $fileSuffix;
3298  } while (file_exists($tempFileName));
3299  touch($tempFileName);
3300  clearstatcache(false, $tempFileName);
3301  }
3302  return $tempFileName;
3303  }
3304 
3313  public static function stdAuthCode($uid_or_record, ‪$fields = '', $codeLength = 8)
3314  {
3315  if (is_array($uid_or_record)) {
3316  $recCopy_temp = [];
3317  if (‪$fields) {
3318  $fieldArr = ‪self::trimExplode(',', ‪$fields, true);
3319  foreach ($fieldArr as $k => $v) {
3320  $recCopy_temp[$k] = $uid_or_record[$v];
3321  }
3322  } else {
3323  $recCopy_temp = $uid_or_record;
3324  }
3325  $preKey = implode('|', $recCopy_temp);
3326  } else {
3327  $preKey = $uid_or_record;
3328  }
3329  $authCode = $preKey . '||' . ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'];
3330  $authCode = substr(md5($authCode), 0, $codeLength);
3331  return $authCode;
3332  }
3333 
3340  public static function hideIfNotTranslated($l18n_cfg_fieldValue)
3341  {
3342  return ‪$GLOBALS['TYPO3_CONF_VARS']['FE']['hidePagesIfNotTranslatedByDefault'] xor ($l18n_cfg_fieldValue & 2);
3343  }
3344 
3352  public static function hideIfDefaultLanguage($localizationConfiguration)
3353  {
3354  return (bool)($localizationConfiguration & 1);
3355  }
3356 
3367  public static function callUserFunction($funcName, &$params, &$ref = null)
3368  {
3369  if (!($ref === null || is_object($ref))) {
3370  trigger_error(
3371  sprintf('Argument "$ref" is of type "%s" which is deprecated since TYPO3 10.3. "$ref" must be of type "object" or null as of version 11.0', gettype($ref)),
3372  E_USER_DEPRECATED
3373  );
3374  }
3375 
3376  // Check if we're using a closure and invoke it directly.
3377  if (is_object($funcName) && is_a($funcName, \Closure::class)) {
3378  return call_user_func_array($funcName, [&$params, &$ref]);
3379  }
3380  $funcName = trim($funcName);
3381  $parts = explode('->', $funcName);
3382  // Call function or method
3383  if (count($parts) === 2) {
3384  // It's a class/method
3385  // Check if class/method exists:
3386  if (class_exists($parts[0])) {
3387  // Create object
3388  $classObj = self::makeInstance($parts[0]);
3389  $methodName = (string)$parts[1];
3390  $callable = [$classObj, $methodName];
3391  if (is_callable($callable)) {
3392  // Call method:
3393  $content = call_user_func_array($callable, [&$params, &$ref]);
3394  } else {
3395  $errorMsg = 'No method name \'' . $parts[1] . '\' in class ' . $parts[0];
3396  throw new \InvalidArgumentException($errorMsg, 1294585865);
3397  }
3398  } else {
3399  $errorMsg = 'No class named ' . $parts[0];
3400  throw new \InvalidArgumentException($errorMsg, 1294585866);
3401  }
3402  } elseif (function_exists($funcName) && is_callable($funcName)) {
3403  // It's a function
3404  $content = call_user_func_array($funcName, [&$params, &$ref]);
3405  } else {
3406  $errorMsg = 'No function named: ' . $funcName;
3407  throw new \InvalidArgumentException($errorMsg, 1294585867);
3408  }
3409  return $content;
3410  }
3411 
3416  public static function setContainer(ContainerInterface ‪$container): void
3417  {
3418  self::$container = ‪$container;
3419  }
3420 
3425  public static function getContainer(): ContainerInterface
3426  {
3427  if (self::$container === null) {
3428  throw new \LogicException('PSR-11 Container is not available', 1549404144);
3429  }
3430  return ‪self::$container;
3431  }
3432 
3453  public static function makeInstance($className, ...$constructorArguments)
3454  {
3455  if (!is_string($className) || empty($className)) {
3456  throw new \InvalidArgumentException('$className must be a non empty string.', 1288965219);
3457  }
3458  // Never instantiate with a beginning backslash, otherwise things like singletons won't work.
3459  if ($className[0] === '\\') {
3460  throw new \InvalidArgumentException(
3461  '$className "' . $className . '" must not start with a backslash.',
3462  1420281366
3463  );
3464  }
3465  if (isset(static::$finalClassNameCache[$className])) {
3466  $finalClassName = static::$finalClassNameCache[$className];
3467  } else {
3468  $finalClassName = self::getClassName($className);
3469  static::$finalClassNameCache[$className] = $finalClassName;
3470  }
3471  // Return singleton instance if it is already registered
3472  if (isset(self::$singletonInstances[$finalClassName])) {
3473  return self::$singletonInstances[$finalClassName];
3474  }
3475  // Return instance if it has been injected by addInstance()
3476  if (
3477  isset(self::$nonSingletonInstances[$finalClassName])
3478  && !empty(self::$nonSingletonInstances[$finalClassName])
3479  ) {
3480  return array_shift(self::$nonSingletonInstances[$finalClassName]);
3481  }
3482 
3483  // Read service and prototypes from the DI container, this is required to
3484  // support classes that require dependency injection.
3485  // We operate on the original class name on purpose, as class overrides
3486  // are resolved inside the container
3487  if (self::$container !== null && $constructorArguments === [] && self::$container->has($className)) {
3488  return self::$container->get($className);
3489  }
3490 
3491  // Create new instance and call constructor with parameters
3492  $instance = new $finalClassName(...$constructorArguments);
3493  // Register new singleton instance, but only if it is not a known PSR-11 container service
3494  if ($instance instanceof SingletonInterface && !(self::$container !== null && self::$container->has($className))) {
3495  self::$singletonInstances[$finalClassName] = $instance;
3496  }
3497  if ($instance instanceof LoggerAwareInterface) {
3498  $instance->setLogger(static::makeInstance(LogManager::class)->getLogger($className));
3499  }
3500  return $instance;
3501  }
3502 
3515  public static function makeInstanceForDi(string $className, ...$constructorArguments): object
3516  {
3517  $finalClassName = static::$finalClassNameCache[$className] ?? static::$finalClassNameCache[$className] = self::getClassName($className);
3518 
3519  // Return singleton instance if it is already registered (currently required for unit and functional tests)
3520  if (isset(self::$singletonInstances[$finalClassName])) {
3521  return self::$singletonInstances[$finalClassName];
3522  }
3523  // Create new instance and call constructor with parameters
3524  return new $finalClassName(...$constructorArguments);
3525  }
3526 
3534  protected static function getClassName($className)
3535  {
3536  if (class_exists($className)) {
3537  while (static::classHasImplementation($className)) {
3538  $className = static::getImplementationForClass($className);
3539  }
3540  }
3542  }
3543 
3550  protected static function getImplementationForClass($className)
3551  {
3552  return ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][$className]['className'];
3553  }
3554 
3561  protected static function classHasImplementation($className)
3562  {
3563  return !empty(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][$className]['className']);
3564  }
3565 
3583  public static function setSingletonInstance($className, SingletonInterface $instance)
3584  {
3585  self::checkInstanceClassName($className, $instance);
3586  // Check for XCLASS registration (same is done in makeInstance() in order to store the singleton of the final class name)
3587  $finalClassName = self::getClassName($className);
3588  self::$singletonInstances[$finalClassName] = $instance;
3589  }
3590 
3606  public static function removeSingletonInstance($className, SingletonInterface $instance)
3607  {
3608  self::checkInstanceClassName($className, $instance);
3609  if (!isset(self::$singletonInstances[$className])) {
3610  throw new \InvalidArgumentException('No Instance registered for ' . $className . '.', 1394099179);
3611  }
3612  if ($instance !== self::$singletonInstances[$className]) {
3613  throw new \InvalidArgumentException('The instance you are trying to remove has not been registered before.', 1394099256);
3614  }
3615  unset(self::$singletonInstances[$className]);
3616  }
3617 
3631  public static function resetSingletonInstances(array $newSingletonInstances)
3632  {
3633  static::$singletonInstances = [];
3634  foreach ($newSingletonInstances as $className => $instance) {
3635  static::setSingletonInstance($className, $instance);
3636  }
3637  }
3638 
3651  public static function getSingletonInstances()
3652  {
3653  return static::$singletonInstances;
3654  }
3655 
3667  public static function getInstances()
3668  {
3669  return static::$nonSingletonInstances;
3670  }
3671 
3686  public static function addInstance($className, $instance)
3687  {
3688  self::checkInstanceClassName($className, $instance);
3689  if ($instance instanceof SingletonInterface) {
3690  throw new \InvalidArgumentException('$instance must not be an instance of TYPO3\\CMS\\Core\\SingletonInterface. For setting singletons, please use setSingletonInstance.', 1288969325);
3691  }
3692  if (!isset(self::$nonSingletonInstances[$className])) {
3693  self::$nonSingletonInstances[$className] = [];
3694  }
3695  self::$nonSingletonInstances[$className][] = $instance;
3696  }
3697 
3706  protected static function checkInstanceClassName($className, $instance)
3707  {
3708  if ($className === '') {
3709  throw new \InvalidArgumentException('$className must not be empty.', 1288967479);
3710  }
3711  if (!$instance instanceof $className) {
3712  throw new \InvalidArgumentException('$instance must be an instance of ' . $className . ', but actually is an instance of ' . get_class($instance) . '.', 1288967686);
3713  }
3714  }
3715 
3726  public static function purgeInstances()
3727  {
3728  self::$container = null;
3729  self::$singletonInstances = [];
3730  self::$nonSingletonInstances = [];
3731  }
3732 
3742  public static function flushInternalRuntimeCaches()
3743  {
3744  self::$finalClassNameCache = [];
3745  self::$indpEnvCache = [];
3746  }
3747 
3758  public static function makeInstanceService($serviceType, $serviceSubType = '', $excludeServiceKeys = [])
3759  {
3760  $error = false;
3761  if (!is_array($excludeServiceKeys)) {
3762  trigger_error('GeneralUtility::makeInstanceService expects the third method argument to be an array instead of a comma-separated string. TYPO3 v11.0 will only support arrays as third argument for $excludeServiceKeys', E_USER_DEPRECATED);
3763  $excludeServiceKeys = ‪self::trimExplode(',', $excludeServiceKeys, true);
3764  }
3765  $requestInfo = [
3766  'requestedServiceType' => $serviceType,
3767  'requestedServiceSubType' => $serviceSubType,
3768  'requestedExcludeServiceKeys' => $excludeServiceKeys
3769  ];
3770  while ($info = ‪ExtensionManagementUtility::findService($serviceType, $serviceSubType, $excludeServiceKeys)) {
3771  // provide information about requested service to service object
3772  $info = array_merge($info, $requestInfo);
3773  $obj = self::makeInstance($info['className']);
3774  if (is_object($obj)) {
3775  if (!@is_callable([$obj, 'init'])) {
3776  self::getLogger()->error('Requested service ' . $info['className'] . ' has no init() method.', ['service' => $info]);
3777  throw new \RuntimeException('Broken service: ' . $info['className'], 1568119209);
3778  }
3779  $obj->info = $info;
3780  // service available?
3781  if ($obj->init()) {
3782  return $obj;
3783  }
3784  $error = $obj->getLastErrorArray();
3785  unset($obj);
3786  }
3787 
3788  // deactivate the service
3789  ‪ExtensionManagementUtility::deactivateService($info['serviceType'], $info['serviceKey']);
3790  }
3791  return $error;
3792  }
3793 
3800  public static function quoteJSvalue($value)
3801  {
3802  $json = (string)json_encode(
3803  (string)$value,
3804  JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_TAG
3805  );
3806 
3807  return strtr(
3808  $json,
3809  [
3810  '"' => '\'',
3811  '\\\\' => '\\u005C',
3812  ' ' => '\\u0020',
3813  '!' => '\\u0021',
3814  '\\t' => '\\u0009',
3815  '\\n' => '\\u000A',
3816  '\\r' => '\\u000D'
3817  ]
3818  );
3819  }
3820 
3826  public static function jsonEncodeForHtmlAttribute($value, bool $useHtmlEntities = true): string
3827  {
3828  $json = (string)json_encode($value, JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_TAG);
3829  return $useHtmlEntities ? htmlspecialchars($json) : $json;
3830  }
3831 
3842  public static function presetApplicationContext(ApplicationContext $applicationContext)
3843  {
3844  if (static::$applicationContext === null) {
3845  static::$applicationContext = $applicationContext;
3846  } else {
3847  throw new \RuntimeException('Trying to override applicationContext which has already been defined!', 1376084316);
3848  }
3849  }
3850 
3859  public static function resetApplicationContext(): void
3860  {
3861  static::$applicationContext = null;
3862  }
3863 
3870  public static function getApplicationContext()
3871  {
3872  trigger_error('GeneralUtility::getApplicationContext() has been superseded by Environment API. This method will be removed in TYPO3 v11. Use Environment::getContext() instead.', E_USER_DEPRECATED);
3873  // Implicitly setting the application context here, but only if it is used, otherwise this does not
3874  // need to be populated.
3875  if (static::$applicationContext === null) {
3876  static::$applicationContext = ‪Environment::getContext();
3877  }
3878  return static::$applicationContext;
3879  }
3880 
3886  public static function isRunningOnCgiServerApi()
3887  {
3888  trigger_error('GeneralUtility::isRunningOnCgiServerApi() will be removed in TYPO3 v11.0. Use "Environment::isRunningOnCgiServer()" instead.', E_USER_DEPRECATED);
3890  }
3891 
3895  protected static function getLogger()
3896  {
3897  return static::makeInstance(LogManager::class)->getLogger(__CLASS__);
3898  }
3899 }
‪TYPO3\CMS\Core\Utility\GeneralUtility\underscoredToLowerCamelCase
‪static string underscoredToLowerCamelCase($string)
Definition: GeneralUtility.php:902
‪TYPO3\CMS\Core\Utility\GeneralUtility\$spaceInd
‪static array< string, function explodeUrl2Array( $string) { $output=[];$p=explode('&', $string);foreach( $p as $v) { if( $v !=='') {[ $pK, $pV]=explode('=', $v, 2);$output[rawurldecode( $pK)]=rawurldecode( $pV);} } return $output;} public static array function compileSelectedGetVarsFromArray( $varList, array $getArray, $GPvarAlt=true) { $keys=self::trimExplode(',', $varList, true);$outArr=[];foreach( $keys as $v) { if(isset( $getArray[ $v])) { $outArr[ $v]=$getArray[ $v];} elseif( $GPvarAlt) { $outArr[ $v]=self::_GP( $v);} } return $outArr;} public static array function removeDotsFromTS(array $ts) { $out=[];foreach( $ts as $key=> $value) { if(is_array( $value)) { $key=rtrim( $key, '.');$out[ $key]=self::removeDotsFromTS( $value);} else { $out[ $key]=$value;} } return $out;} public static array< string, function get_tag_attributes( $tag, bool $decodeEntities=false) { $components=self::split_tag_attributes( $tag);$name='';$valuemode=false;$attributes=[];foreach( $components as $key=> $val) { if( $val !=='=') { if( $valuemode) { if( $name) { $attributes[ $name]=$decodeEntities ? htmlspecialchars_decode( $val) :$val;$name='';} } else { if( $key=strtolower(preg_replace('/[^[:alnum:]_\\:\\-]/', '', $val) ?? '')) { $attributes[ $key]='';$name=$key;} } $valuemode=false;} else { $valuemode=true;} } return $attributes;} public static string[] function split_tag_attributes( $tag) { $tag_tmp=trim(preg_replace('/^<[^[:space:]] */', '', trim( $tag)) ?? '');$tag_tmp=trim(rtrim( $tag_tmp, '>'));$value=[];while( $tag_tmp !=='') { $firstChar=$tag_tmp[0];if( $firstChar==='"' || $firstChar === '\'') { $reg = explode($firstChar, $tag_tmp, 3); $value[] = $reg[1]; $tag_tmp = trim($reg[2]); } elseif ($firstChar === '=') { $value[] = '='; $tag_tmp = trim(substr($tag_tmp, 1)); } else { $reg = preg_split('/[[:space:]=]/', $tag_tmp, 2); $value[] = trim($reg[0]); $tag_tmp = trim(substr($tag_tmp, strlen($reg[0]), 1) . ($reg[1] ?? '')); } } reset($value); return $value; } public static string function implodeAttributes(array $arr, $xhtmlSafe = false, $dontOmitBlankAttribs = false) { if ($xhtmlSafe) { $newArr = []; foreach ($arr as $p => $v) { if (!isset($newArr[strtolower($p)])) { $newArr[strtolower($p)] = htmlspecialchars($v); } } $arr = $newArr; } $list = []; foreach ($arr as $p => $v) { if ((string)$v !== '' || $dontOmitBlankAttribs) { $list[] = $p . '="' . $v . '"'; } } return implode(' ', $list); } public static string function wrapJS($string) { if (trim($string)) { $string = ltrim($string, LF); $match = []; if (preg_match('/^(\\t+)/', $string, $match)) { $string = str_replace($match[1], "\t", $string); } return '<script>' . $string . '</script>'; } return ''; } public static mixed function xml2tree($string, $depth = 999, $parserOptions = []) { $previousValueOfEntityLoader = null; if (PHP_MAJOR_VERSION < 8) { $previousValueOfEntityLoader = libxml_disable_entity_loader(true); } $parser = xml_parser_create(); $vals = []; $index = []; xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0); xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 0); foreach ($parserOptions as $option => $value) { xml_parser_set_option($parser, $option, $value); } xml_parse_into_struct($parser, $string, $vals, $index); if (PHP_MAJOR_VERSION < 8) { libxml_disable_entity_loader($previousValueOfEntityLoader); } if (xml_get_error_code($parser)) { return 'Line ' . xml_get_current_line_number($parser) . ': ' . xml_error_string(xml_get_error_code($parser)); } xml_parser_free($parser); $stack = [[]]; $stacktop = 0; $startPoint = 0; $tagi = []; foreach ($vals as $key => $val) { $type = $val['type']; if ($type === 'open' || $type === 'complete') { $stack[$stacktop++] = $tagi; if ($depth == $stacktop) { $startPoint = $key; } $tagi = ['tag' => $val['tag']]; if (isset($val['attributes'])) { $tagi['attrs'] = $val['attributes']; } if (isset($val['value'])) { $tagi['values'][] = $val['value']; } } if ($type === 'complete' || $type === 'close') { $oldtagi = $tagi; $tagi = $stack[--$stacktop]; $oldtag = $oldtagi['tag']; unset($oldtagi['tag']); if ($depth == $stacktop + 1) { if ($key - $startPoint > 0) { $partArray = array_slice($vals, $startPoint + 1, $key - $startPoint - 1); $oldtagi['XMLvalue'] = self::xmlRecompileFromStructValArray($partArray); } else { $oldtagi['XMLvalue'] = $oldtagi['values'][0]; } } $tagi['ch'][$oldtag][] = $oldtagi; unset($oldtagi); } if ($type === 'cdata') { $tagi['values'][] = $val['value']; } } return $tagi['ch']; } public static string function array2xml(array $array, $NSprefix = '', $level = 0, $docTag = 'phparray', $spaceInd = 0, array $options = [], array $stackData = []) { $binaryChars = "\0" . chr(1) . chr(2) . chr(3) . chr(4) . chr(5) . chr(6) . chr(7) . chr(8) . chr(11) . chr(12) . chr(14) . chr(15) . chr(16) . chr(17) . chr(18) . chr(19) . chr(20) . chr(21) . chr(22) . chr(23) . chr(24) . chr(25) . chr(26) . chr(27) . chr(28) . chr(29) . chr(30) . chr(31); $indentChar = $spaceInd ? ' ' : "\t"; $indentN = $spaceInd > $spaceInd
Definition: GeneralUtility.php:1419
‪TYPO3\CMS\Core\Utility\GeneralUtility\xml2array
‪static mixed xml2array($string, $NSprefix='', $reportDocTag=false)
Definition: GeneralUtility.php:1531
‪TYPO3\CMS\Core\Utility\HttpUtility\idn_to_ascii
‪static string bool idn_to_ascii(string $domain)
Definition: HttpUtility.php:195
‪TYPO3\CMS\Core\Utility\GeneralUtility\$nl
‪$nl
Definition: GeneralUtility.php:1420
‪TYPO3\CMS\Core\Utility\MathUtility\canBeInterpretedAsInteger
‪static bool canBeInterpretedAsInteger($var)
Definition: MathUtility.php:74
‪TYPO3\CMS\Core\Core\Environment\getPublicPath
‪static string getPublicPath()
Definition: Environment.php:180
‪TYPO3\CMS\Core\Utility\GeneralUtility\minifyJavaScript
‪static string minifyJavaScript($script, &$error='')
Definition: GeneralUtility.php:1713
‪TYPO3\CMS\Core\Core\Environment\isRunningOnCgiServer
‪static bool isRunningOnCgiServer()
Definition: Environment.php:312
‪TYPO3\CMS\Core\Core\ApplicationContext
Definition: ApplicationContext.php:37
‪TYPO3\CMS\Core\Exception
Definition: Exception.php:22
‪TYPO3\CMS\Core\Resource\Security\FileNameValidator
Definition: FileNameValidator.php:25
‪TYPO3\CMS\Core\Utility\PathUtility\stripPathSitePrefix
‪static string stripPathSitePrefix($path)
Definition: PathUtility.php:372
‪TYPO3
‪TYPO3\CMS\Core\Utility\GeneralUtility\$allowHostHeaderValue
‪static bool $allowHostHeaderValue
Definition: GeneralUtility.php:55
‪if
‪if(PHP_SAPI !=='cli')
Definition: splitAcceptanceTests.php:33
‪TYPO3\CMS\Core\Utility\GeneralUtility\$localeInfo
‪$localeInfo
Definition: GeneralUtility.php:798
‪TYPO3\CMS\Core\Utility\GeneralUtility\getUrl
‪static mixed getUrl($url, $includeHeader=0, $requestHeaders=null, &$report=null)
Definition: GeneralUtility.php:1748
‪TYPO3\CMS\Core\Utility\PathUtility\dirnameDuringBootstrap
‪static string dirnameDuringBootstrap($path)
Definition: PathUtility.php:276
‪TYPO3\CMS\Core\Core\Environment\isWindows
‪static bool isWindows()
Definition: Environment.php:292
‪TYPO3\CMS\Core\Utility
Definition: ArrayUtility.php:16
‪TYPO3\CMS\Core\Core\ClassLoadingInformation
Definition: ClassLoadingInformation.php:35
‪TYPO3\CMS\Core\Utility\ExtensionManagementUtility\findService
‪static mixed findService($serviceType, $serviceSubType='', $excludeServiceKeys=[])
Definition: ExtensionManagementUtility.php:1119
‪TYPO3\CMS\Core\Utility\GeneralUtility\$labelArr
‪if($base !==1000 && $base !==1024) $labelArr
Definition: GeneralUtility.php:795
‪$parser
‪$parser
Definition: annotationChecker.php:108
‪TYPO3\CMS\Core\Core\Environment\getCurrentScript
‪static string getCurrentScript()
Definition: Environment.php:220
‪TYPO3\CMS\Core\Utility\GeneralUtility\flushDirectory
‪static bool flushDirectory($directory, $keepOriginalDirectory=false, $flushOpcodeCache=false)
Definition: GeneralUtility.php:2135
‪TYPO3\CMS\Core\Utility\GeneralUtility\$multiplier
‪$multiplier
Definition: GeneralUtility.php:800
‪TYPO3\CMS\Core\Utility\ArrayUtility\mergeRecursiveWithOverrule
‪static mergeRecursiveWithOverrule(array &$original, array $overrule, $addKeys=true, $includeEmptyValues=true, $enableUnsetFeature=true)
Definition: ArrayUtility.php:654
‪TYPO3\CMS\Core\Utility\GeneralUtility\camelCaseToLowerCaseUnderscored
‪static string camelCaseToLowerCaseUnderscored($string)
Definition: GeneralUtility.php:914
‪$dir
‪$dir
Definition: validateRstFiles.php:213
‪TYPO3\CMS\Core\Utility\GeneralUtility\createDirectoryPath
‪static string createDirectoryPath($fullDirectoryPath)
Definition: GeneralUtility.php:2048
‪$fields
‪$fields
Definition: pages.php:5
‪TYPO3\CMS\Core\Utility\PathUtility\basename
‪static string basename($path)
Definition: PathUtility.php:165
‪TYPO3\CMS\Core\Utility\GeneralUtility\implodeArrayForUrl
‪static string implodeArrayForUrl($name, array $theArray, $str='', $skipBlank=false, $rawurlencodeParamName=false)
Definition: GeneralUtility.php:1097
‪TYPO3\CMS\Core\Core\Environment\getContext
‪static ApplicationContext getContext()
Definition: Environment.php:133
‪TYPO3\CMS\Core\Utility\GeneralUtility\get_dirs
‪static string[] string null get_dirs($path)
Definition: GeneralUtility.php:2170
‪TYPO3\CMS\Core\Core\ClassLoadingInformation\getClassNameForAlias
‪static mixed getClassNameForAlias($alias)
Definition: ClassLoadingInformation.php:207
‪TYPO3\CMS\Core\Utility\GeneralUtility\$sizeInUnits
‪$sizeInUnits
Definition: GeneralUtility.php:801
‪TYPO3\CMS\Core\Utility\HttpUtility\buildUrl
‪static string buildUrl(array $urlParts)
Definition: HttpUtility.php:141
‪TYPO3\CMS\Core\Core\Environment\getProjectPath
‪static string getProjectPath()
Definition: Environment.php:169
‪TYPO3\CMS\Core\Utility\GeneralUtility\ENV_TRUSTED_HOSTS_PATTERN_ALLOW_ALL
‪const ENV_TRUSTED_HOSTS_PATTERN_ALLOW_ALL
Definition: GeneralUtility.php:47
‪TYPO3\CMS\Core\Utility\ExtensionManagementUtility\deactivateService
‪static deactivateService($serviceType, $serviceKey)
Definition: ExtensionManagementUtility.php:1220
‪TYPO3\CMS\Core\Utility\GeneralUtility\$output
‪foreach($array as $k=> $v) if(! $level) return $output
Definition: GeneralUtility.php:1513
‪TYPO3\CMS\Core\Utility\GeneralUtility\fixPermissions
‪static mixed fixPermissions($path, $recursive=false)
Definition: GeneralUtility.php:1863
‪TYPO3\CMS\Core\Utility\GeneralUtility\mkdir_deep
‪static mkdir_deep($directory)
Definition: GeneralUtility.php:2022
‪$validator
‪if(isset($args['d'])) $validator
Definition: validateRstFiles.php:218
‪TYPO3\CMS\Core\Utility\GeneralUtility\idnaEncode
‪static string idnaEncode($value)
Definition: GeneralUtility.php:863
‪TYPO3\CMS\Core\Utility\GeneralUtility\writeFileToTypo3tempDir
‪static string writeFileToTypo3tempDir($filepath, $content)
Definition: GeneralUtility.php:1928
‪TYPO3\CMS\Core\Utility\GeneralUtility\$container
‪static ContainerInterface null $container
Definition: GeneralUtility.php:59
‪TYPO3\CMS\Core\Cache\CacheManager
Definition: CacheManager.php:35
‪TYPO3\CMS\Core\Utility\GeneralUtility\xml2arrayProcess
‪static mixed xml2arrayProcess($string, $NSprefix='', $reportDocTag=false)
Definition: GeneralUtility.php:1554
‪TYPO3\CMS\Core\Service\OpcodeCacheService
Definition: OpcodeCacheService.php:25
‪TYPO3\CMS\Core\Utility\GeneralUtility\$sizeInBytes
‪$sizeInBytes
Definition: GeneralUtility.php:799
‪TYPO3\CMS\Core\Http\RequestFactory
Definition: RequestFactory.php:31
‪TYPO3\CMS\Core\Utility\PathUtility\isAbsolutePath
‪static bool isAbsolutePath($path)
Definition: PathUtility.php:223
‪TYPO3\CMS\Core\Utility\GeneralUtility\ENV_TRUSTED_HOSTS_PATTERN_SERVER_NAME
‪const ENV_TRUSTED_HOSTS_PATTERN_SERVER_NAME
Definition: GeneralUtility.php:48
‪TYPO3\CMS\Core\Utility\GeneralUtility\trimExplode
‪static string[] trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
Definition: GeneralUtility.php:1059
‪TYPO3\CMS\Core\Utility\GeneralUtility\$output
‪$output
Definition: GeneralUtility.php:1422
‪TYPO3\CMS\Core\Utility\GeneralUtility\isValidUrl
‪static bool isValidUrl($url)
Definition: GeneralUtility.php:944
‪TYPO3\CMS\Core\Utility\StringUtility\getUniqueId
‪static string getUniqueId($prefix='')
Definition: StringUtility.php:92
‪TYPO3\CMS\Core\SingletonInterface
Definition: SingletonInterface.php:23
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:5
‪TYPO3\CMS\Core\Utility\GeneralUtility\revExplode
‪static string[] revExplode($delimiter, $string, $count=0)
Definition: GeneralUtility.php:1025
‪TYPO3\CMS\Core\Log\LogManager
Definition: LogManager.php:30
‪TYPO3\CMS\Core\Core\Environment
Definition: Environment.php:40
‪TYPO3\CMS\Core\Utility\GeneralUtility\intExplode
‪static int[] intExplode($delimiter, $string, $removeEmptyValues=false, $limit=0)
Definition: GeneralUtility.php:988
‪TYPO3\CMS\Core\Utility\GeneralUtility\rmdir
‪static bool rmdir($path, $removeNonEmpty=false)
Definition: GeneralUtility.php:2075
‪TYPO3\CMS\Core\Utility\ExtensionManagementUtility\extPath
‪static string extPath($key, $script='')
Definition: ExtensionManagementUtility.php:127
‪TYPO3\CMS\Core\Utility\GeneralUtility\xmlRecompileFromStructValArray
‪static string xmlRecompileFromStructValArray(array $vals)
Definition: GeneralUtility.php:1668
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:46
‪TYPO3\CMS\Core\Utility\GeneralUtility\mkdir
‪static bool mkdir($newFolder)
Definition: GeneralUtility.php:2005
‪TYPO3\CMS\Core\Utility\GeneralUtility\writeFile
‪static bool writeFile($file, $content, $changePermissions=false)
Definition: GeneralUtility.php:1836
‪TYPO3\CMS\Core\Utility\ExtensionManagementUtility\isLoaded
‪static bool isLoaded($key)
Definition: ExtensionManagementUtility.php:114
‪TYPO3\CMS\Core\Core\Environment\isCli
‪static bool isCli()
Definition: Environment.php:154
‪TYPO3\CMS\Core\Utility\GeneralUtility\underscoredToUpperCamelCase
‪static string underscoredToUpperCamelCase($string)
Definition: GeneralUtility.php:890
‪TYPO3\CMS\Core\Core\Environment\getVarPath
‪static string getVarPath()
Definition: Environment.php:192