TYPO3CMS  8
 All Classes Namespaces Files Functions Variables Pages
GeneralUtility.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Core\Utility;
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
17 use GuzzleHttp\Exception\RequestException;
26 
40 {
41  // Severity constants used by \TYPO3\CMS\Core\Utility\GeneralUtility::sysLog()
47 
50 
57  protected static $allowHostHeaderValue = false;
58 
65  protected static $singletonInstances = [];
66 
72  protected static $nonSingletonInstances = [];
73 
79  protected static $finalClassNameCache = [];
80 
86  protected static $applicationContext = null;
87 
93  protected static $idnaStringCache = [];
94 
100  protected static $idnaConverter = null;
101 
107  protected static $supportedCgiServerApis = [
108  'fpm-fcgi',
109  'cgi',
110  'isapi',
111  'cgi-fcgi',
112  'srv', // HHVM with fastcgi
113  ];
114 
118  protected static $indpEnvCache = [];
119 
120  /*************************
121  *
122  * GET/POST Variables
123  *
124  * Background:
125  * Input GET/POST variables in PHP may have their quotes escaped with "\" or not depending on configuration.
126  * TYPO3 has always converted quotes to BE escaped if the configuration told that they would not be so.
127  * But the clean solution is that quotes are never escaped and that is what the functions below offers.
128  * Eventually TYPO3 should provide this in the global space as well.
129  * In the transitional phase (or forever..?) we need to encourage EVERY to read and write GET/POST vars through the API functions below.
130  * This functionality was previously needed to normalize between magic quotes logic, which was removed from PHP 5.4,
131  * so these methods are still in use, but not tackle the slash problem anymore.
132  *
133  *************************/
142  public static function _GP($var)
143  {
144  if (empty($var)) {
145  return;
146  }
147  if (isset($_POST[$var])) {
148  $value = $_POST[$var];
149  } elseif (isset($_GET[$var])) {
150  $value = $_GET[$var];
151  } else {
152  $value = null;
153  }
154  // This is there for backwards-compatibility, in order to avoid NULL
155  if (isset($value) && !is_array($value)) {
156  $value = (string)$value;
157  }
158  return $value;
159  }
160 
167  public static function _GPmerged($parameter)
168  {
169  $postParameter = isset($_POST[$parameter]) && is_array($_POST[$parameter]) ? $_POST[$parameter] : [];
170  $getParameter = isset($_GET[$parameter]) && is_array($_GET[$parameter]) ? $_GET[$parameter] : [];
171  $mergedParameters = $getParameter;
172  ArrayUtility::mergeRecursiveWithOverrule($mergedParameters, $postParameter);
173  return $mergedParameters;
174  }
175 
185  public static function _GET($var = null)
186  {
187  $value = $var === null ? $_GET : (empty($var) ? null : $_GET[$var]);
188  // This is there for backwards-compatibility, in order to avoid NULL
189  if (isset($value) && !is_array($value)) {
190  $value = (string)$value;
191  }
192  return $value;
193  }
194 
203  public static function _POST($var = null)
204  {
205  $value = $var === null ? $_POST : (empty($var) ? null : $_POST[$var]);
206  // This is there for backwards-compatibility, in order to avoid NULL
207  if (isset($value) && !is_array($value)) {
208  $value = (string)$value;
209  }
210  return $value;
211  }
212 
220  public static function _GETset($inputGet, $key = '')
221  {
222  if ($key != '') {
223  if (strpos($key, '|') !== false) {
224  $pieces = explode('|', $key);
225  $newGet = [];
226  $pointer = &$newGet;
227  foreach ($pieces as $piece) {
228  $pointer = &$pointer[$piece];
229  }
230  $pointer = $inputGet;
231  $mergedGet = $_GET;
232  ArrayUtility::mergeRecursiveWithOverrule($mergedGet, $newGet);
233  $_GET = $mergedGet;
234  $GLOBALS['HTTP_GET_VARS'] = $mergedGet;
235  } else {
236  $_GET[$key] = $inputGet;
237  $GLOBALS['HTTP_GET_VARS'][$key] = $inputGet;
238  }
239  } elseif (is_array($inputGet)) {
240  $_GET = $inputGet;
241  $GLOBALS['HTTP_GET_VARS'] = $inputGet;
242  }
243  }
244 
255  public static function removeXSS($string)
256  {
257  return \RemoveXSS::process($string);
258  }
259 
260  /*************************
261  *
262  * IMAGE FUNCTIONS
263  *
264  *************************/
265 
266  /*************************
267  *
268  * STRING FUNCTIONS
269  *
270  *************************/
279  public static function fixed_lgd_cs($string, $chars, $appendString = '...')
280  {
282  $charsetConverter = self::makeInstance(\TYPO3\CMS\Core\Charset\CharsetConverter::class);
283  return $charsetConverter->crop('utf-8', $string, $chars, $appendString);
284  }
285 
294  public static function cmpIP($baseIP, $list)
295  {
296  $list = trim($list);
297  if ($list === '') {
298  return false;
299  } elseif ($list === '*') {
300  return true;
301  }
302  if (strpos($baseIP, ':') !== false && self::validIPv6($baseIP)) {
303  return self::cmpIPv6($baseIP, $list);
304  } else {
305  return self::cmpIPv4($baseIP, $list);
306  }
307  }
308 
316  public static function cmpIPv4($baseIP, $list)
317  {
318  $IPpartsReq = explode('.', $baseIP);
319  if (count($IPpartsReq) === 4) {
320  $values = self::trimExplode(',', $list, true);
321  foreach ($values as $test) {
322  $testList = explode('/', $test);
323  if (count($testList) === 2) {
324  list($test, $mask) = $testList;
325  } else {
326  $mask = false;
327  }
328  if ((int)$mask) {
329  // "192.168.3.0/24"
330  $lnet = ip2long($test);
331  $lip = ip2long($baseIP);
332  $binnet = str_pad(decbin($lnet), 32, '0', STR_PAD_LEFT);
333  $firstpart = substr($binnet, 0, $mask);
334  $binip = str_pad(decbin($lip), 32, '0', STR_PAD_LEFT);
335  $firstip = substr($binip, 0, $mask);
336  $yes = $firstpart === $firstip;
337  } else {
338  // "192.168.*.*"
339  $IPparts = explode('.', $test);
340  $yes = 1;
341  foreach ($IPparts as $index => $val) {
342  $val = trim($val);
343  if ($val !== '*' && $IPpartsReq[$index] !== $val) {
344  $yes = 0;
345  }
346  }
347  }
348  if ($yes) {
349  return true;
350  }
351  }
352  }
353  return false;
354  }
355 
363  public static function cmpIPv6($baseIP, $list)
364  {
365  // Policy default: Deny connection
366  $success = false;
367  $baseIP = self::normalizeIPv6($baseIP);
368  $values = self::trimExplode(',', $list, true);
369  foreach ($values as $test) {
370  $testList = explode('/', $test);
371  if (count($testList) === 2) {
372  list($test, $mask) = $testList;
373  } else {
374  $mask = false;
375  }
376  if (self::validIPv6($test)) {
377  $test = self::normalizeIPv6($test);
378  $maskInt = (int)$mask ?: 128;
379  // Special case; /0 is an allowed mask - equals a wildcard
380  if ($mask === '0') {
381  $success = true;
382  } elseif ($maskInt == 128) {
383  $success = $test === $baseIP;
384  } else {
385  $testBin = self::IPv6Hex2Bin($test);
386  $baseIPBin = self::IPv6Hex2Bin($baseIP);
387  $success = true;
388  // Modulo is 0 if this is a 8-bit-boundary
389  $maskIntModulo = $maskInt % 8;
390  $numFullCharactersUntilBoundary = (int)($maskInt / 8);
391  if (substr($testBin, 0, $numFullCharactersUntilBoundary) !== substr($baseIPBin, 0, $numFullCharactersUntilBoundary)) {
392  $success = false;
393  } elseif ($maskIntModulo > 0) {
394  // If not an 8-bit-boundary, check bits of last character
395  $testLastBits = str_pad(decbin(ord(substr($testBin, $numFullCharactersUntilBoundary, 1))), 8, '0', STR_PAD_LEFT);
396  $baseIPLastBits = str_pad(decbin(ord(substr($baseIPBin, $numFullCharactersUntilBoundary, 1))), 8, '0', STR_PAD_LEFT);
397  if (strncmp($testLastBits, $baseIPLastBits, $maskIntModulo) != 0) {
398  $success = false;
399  }
400  }
401  }
402  }
403  if ($success) {
404  return true;
405  }
406  }
407  return false;
408  }
409 
417  public static function IPv6Hex2Bin($hex)
418  {
419  return inet_pton($hex);
420  }
421 
429  public static function IPv6Bin2Hex($bin)
430  {
431  return inet_ntop($bin);
432  }
433 
441  public static function normalizeIPv6($address)
442  {
443  $normalizedAddress = '';
444  $stageOneAddress = '';
445  // According to RFC lowercase-representation is recommended
446  $address = strtolower($address);
447  // Normalized representation has 39 characters (0000:0000:0000:0000:0000:0000:0000:0000)
448  if (strlen($address) == 39) {
449  // Already in full expanded form
450  return $address;
451  }
452  // Count 2 if if address has hidden zero blocks
453  $chunks = explode('::', $address);
454  if (count($chunks) === 2) {
455  $chunksLeft = explode(':', $chunks[0]);
456  $chunksRight = explode(':', $chunks[1]);
457  $left = count($chunksLeft);
458  $right = count($chunksRight);
459  // Special case: leading zero-only blocks count to 1, should be 0
460  if ($left == 1 && strlen($chunksLeft[0]) == 0) {
461  $left = 0;
462  }
463  $hiddenBlocks = 8 - ($left + $right);
464  $hiddenPart = '';
465  $h = 0;
466  while ($h < $hiddenBlocks) {
467  $hiddenPart .= '0000:';
468  $h++;
469  }
470  if ($left == 0) {
471  $stageOneAddress = $hiddenPart . $chunks[1];
472  } else {
473  $stageOneAddress = $chunks[0] . ':' . $hiddenPart . $chunks[1];
474  }
475  } else {
476  $stageOneAddress = $address;
477  }
478  // Normalize the blocks:
479  $blocks = explode(':', $stageOneAddress);
480  $divCounter = 0;
481  foreach ($blocks as $block) {
482  $tmpBlock = '';
483  $i = 0;
484  $hiddenZeros = 4 - strlen($block);
485  while ($i < $hiddenZeros) {
486  $tmpBlock .= '0';
487  $i++;
488  }
489  $normalizedAddress .= $tmpBlock . $block;
490  if ($divCounter < 7) {
491  $normalizedAddress .= ':';
492  $divCounter++;
493  }
494  }
495  return $normalizedAddress;
496  }
497 
505  public static function compressIPv6($address)
506  {
507  return inet_ntop(inet_pton($address));
508  }
509 
518  public static function validIP($ip)
519  {
520  return filter_var($ip, FILTER_VALIDATE_IP) !== false;
521  }
522 
531  public static function validIPv4($ip)
532  {
533  return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false;
534  }
535 
544  public static function validIPv6($ip)
545  {
546  return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false;
547  }
548 
556  public static function cmpFQDN($baseHost, $list)
557  {
558  $baseHost = trim($baseHost);
559  if (empty($baseHost)) {
560  return false;
561  }
562  if (self::validIPv4($baseHost) || self::validIPv6($baseHost)) {
563  // Resolve hostname
564  // Note: this is reverse-lookup and can be randomly set as soon as somebody is able to set
565  // the reverse-DNS for his IP (security when for example used with REMOTE_ADDR)
566  $baseHostName = gethostbyaddr($baseHost);
567  if ($baseHostName === $baseHost) {
568  // Unable to resolve hostname
569  return false;
570  }
571  } else {
572  $baseHostName = $baseHost;
573  }
574  $baseHostNameParts = explode('.', $baseHostName);
575  $values = self::trimExplode(',', $list, true);
576  foreach ($values as $test) {
577  $hostNameParts = explode('.', $test);
578  // To match hostNameParts can only be shorter (in case of wildcards) or equal
579  $hostNamePartsCount = count($hostNameParts);
580  $baseHostNamePartsCount = count($baseHostNameParts);
581  if ($hostNamePartsCount > $baseHostNamePartsCount) {
582  continue;
583  }
584  $yes = true;
585  foreach ($hostNameParts as $index => $val) {
586  $val = trim($val);
587  if ($val === '*') {
588  // Wildcard valid for one or more hostname-parts
589  $wildcardStart = $index + 1;
590  // Wildcard as last/only part always matches, otherwise perform recursive checks
591  if ($wildcardStart < $hostNamePartsCount) {
592  $wildcardMatched = false;
593  $tempHostName = implode('.', array_slice($hostNameParts, $index + 1));
594  while ($wildcardStart < $baseHostNamePartsCount && !$wildcardMatched) {
595  $tempBaseHostName = implode('.', array_slice($baseHostNameParts, $wildcardStart));
596  $wildcardMatched = self::cmpFQDN($tempBaseHostName, $tempHostName);
597  $wildcardStart++;
598  }
599  if ($wildcardMatched) {
600  // Match found by recursive compare
601  return true;
602  } else {
603  $yes = false;
604  }
605  }
606  } elseif ($baseHostNameParts[$index] !== $val) {
607  // In case of no match
608  $yes = false;
609  }
610  }
611  if ($yes) {
612  return true;
613  }
614  }
615  return false;
616  }
617 
625  public static function isOnCurrentHost($url)
626  {
627  return stripos($url . '/', self::getIndpEnv('TYPO3_REQUEST_HOST') . '/') === 0;
628  }
629 
638  public static function inList($list, $item)
639  {
640  return strpos(',' . $list . ',', ',' . $item . ',') !== false;
641  }
642 
653  public static function rmFromList($element, $list)
654  {
655  $items = explode(',', $list);
656  foreach ($items as $k => $v) {
657  if ($v == $element) {
658  unset($items[$k]);
659  }
660  }
661  return implode(',', $items);
662  }
663 
671  public static function expandList($list)
672  {
673  $items = explode(',', $list);
674  $list = [];
675  foreach ($items as $item) {
676  $range = explode('-', $item);
677  if (isset($range[1])) {
678  $runAwayBrake = 1000;
679  for ($n = $range[0]; $n <= $range[1]; $n++) {
680  $list[] = $n;
681  $runAwayBrake--;
682  if ($runAwayBrake <= 0) {
683  break;
684  }
685  }
686  } else {
687  $list[] = $item;
688  }
689  }
690  return implode(',', $list);
691  }
692 
702  public static function compat_version($verNumberStr)
703  {
704  static::logDeprecatedFunction();
706  }
707 
714  public static function md5int($str)
715  {
716  return hexdec(substr(md5($str), 0, 7));
717  }
718 
726  public static function shortMD5($input, $len = 10)
727  {
728  return substr(md5($input), 0, $len);
729  }
730 
738  public static function hmac($input, $additionalSecret = '')
739  {
740  $hashAlgorithm = 'sha1';
741  $hashBlocksize = 64;
742  $hmac = '';
743  $secret = $GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'] . $additionalSecret;
744  if (extension_loaded('hash') && function_exists('hash_hmac') && function_exists('hash_algos') && in_array($hashAlgorithm, hash_algos())) {
745  $hmac = hash_hmac($hashAlgorithm, $input, $secret);
746  } else {
747  // Outer padding
748  $opad = str_repeat(chr(92), $hashBlocksize);
749  // Inner padding
750  $ipad = str_repeat(chr(54), $hashBlocksize);
751  if (strlen($secret) > $hashBlocksize) {
752  // Keys longer than block size are shorten
753  $key = str_pad(pack('H*', call_user_func($hashAlgorithm, $secret)), $hashBlocksize, chr(0));
754  } else {
755  // Keys shorter than block size are zero-padded
756  $key = str_pad($secret, $hashBlocksize, chr(0));
757  }
758  $hmac = call_user_func($hashAlgorithm, ($key ^ $opad) . pack('H*', call_user_func($hashAlgorithm, (($key ^ $ipad) . $input))));
759  }
760  return $hmac;
761  }
762 
771  public static function uniqueList($in_list, $secondParameter = null)
772  {
773  if (is_array($in_list)) {
774  throw new \InvalidArgumentException('TYPO3 Fatal Error: TYPO3\\CMS\\Core\\Utility\\GeneralUtility::uniqueList() does NOT support array arguments anymore! Only string comma lists!', 1270853885);
775  }
776  if (isset($secondParameter)) {
777  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);
778  }
779  return implode(',', array_unique(self::trimExplode(',', $in_list, true)));
780  }
781 
788  public static function split_fileref($fileNameWithPath)
789  {
790  $reg = [];
791  if (preg_match('/(.*\\/)(.*)$/', $fileNameWithPath, $reg)) {
792  $info['path'] = $reg[1];
793  $info['file'] = $reg[2];
794  } else {
795  $info['path'] = '';
796  $info['file'] = $fileNameWithPath;
797  }
798  $reg = '';
799  // If open_basedir is set and the fileName was supplied without a path the is_dir check fails
800  if (!is_dir($fileNameWithPath) && preg_match('/(.*)\\.([^\\.]*$)/', $info['file'], $reg)) {
801  $info['filebody'] = $reg[1];
802  $info['fileext'] = strtolower($reg[2]);
803  $info['realFileext'] = $reg[2];
804  } else {
805  $info['filebody'] = $info['file'];
806  $info['fileext'] = '';
807  }
808  reset($info);
809  return $info;
810  }
811 
827  public static function dirname($path)
828  {
829  $p = self::revExplode('/', $path, 2);
830  return count($p) === 2 ? $p[0] : '';
831  }
832 
840  public static function isFirstPartOfStr($str, $partStr)
841  {
842  return $partStr != '' && strpos((string)$str, (string)$partStr, 0) === 0;
843  }
844 
853  public static function formatSize($sizeInBytes, $labels = '', $base = 0)
854  {
855  $defaultFormats = [
856  'iec' => ['base' => 1024, 'labels' => [' ', ' Ki', ' Mi', ' Gi', ' Ti', ' Pi', ' Ei', ' Zi', ' Yi']],
857  'si' => ['base' => 1000, 'labels' => [' ', ' k', ' M', ' G', ' T', ' P', ' E', ' Z', ' Y']],
858  ];
859  // Set labels and base:
860  if (empty($labels)) {
861  $labels = 'iec';
862  }
863  if (isset($defaultFormats[$labels])) {
864  $base = $defaultFormats[$labels]['base'];
865  $labelArr = $defaultFormats[$labels]['labels'];
866  } else {
867  $base = (int)$base;
868  if ($base !== 1000 && $base !== 1024) {
869  $base = 1024;
870  }
871  $labelArr = explode('|', str_replace('"', '', $labels));
872  }
873  // @todo find out which locale is used for current BE user to cover the BE case as well
874  $oldLocale = setlocale(LC_NUMERIC, 0);
875  $newLocale = isset($GLOBALS['TSFE']) ? $GLOBALS['TSFE']->config['config']['locale_all'] : '';
876  if ($newLocale) {
877  setlocale(LC_NUMERIC, $newLocale);
878  }
879  $localeInfo = localeconv();
880  if ($newLocale) {
881  setlocale(LC_NUMERIC, $oldLocale);
882  }
883  $sizeInBytes = max($sizeInBytes, 0);
884  $multiplier = floor(($sizeInBytes ? log($sizeInBytes) : 0) / log($base));
885  $sizeInUnits = $sizeInBytes / pow($base, $multiplier);
886  if ($sizeInUnits > ($base * .9)) {
887  $multiplier++;
888  }
889  $multiplier = min($multiplier, count($labelArr) - 1);
890  $sizeInUnits = $sizeInBytes / pow($base, $multiplier);
891  return number_format($sizeInUnits, (($multiplier > 0) && ($sizeInUnits < 20)) ? 2 : 0, $localeInfo['decimal_point'], '') . $labelArr[$multiplier];
892  }
893 
901  public static function convertMicrotime($microtime)
902  {
903  static::logDeprecatedFunction();
904  $parts = explode(' ', $microtime);
905  return round(($parts[0] + $parts[1]) * 1000);
906  }
907 
916  public static function splitCalc($string, $operators)
917  {
918  $res = [];
919  $sign = '+';
920  while ($string) {
921  $valueLen = strcspn($string, $operators);
922  $value = substr($string, 0, $valueLen);
923  $res[] = [$sign, trim($value)];
924  $sign = substr($string, $valueLen, 1);
925  $string = substr($string, $valueLen + 1);
926  }
927  reset($res);
928  return $res;
929  }
930 
940  public static function deHSCentities($str)
941  {
942  static::logDeprecatedFunction();
943  return preg_replace('/&amp;([#[:alnum:]]*;)/', '&\\1', $str);
944  }
945 
955  public static function slashJS($string, $extended = false, $char = '\'')
956  {
957  static::logDeprecatedFunction();
958  if ($extended) {
959  $string = str_replace('\\', '\\\\', $string);
960  }
961  return str_replace($char, '\\' . $char, $string);
962  }
963 
972  public static function rawUrlEncodeJS($str)
973  {
974  static::logDeprecatedFunction();
975  return str_replace('%20', ' ', rawurlencode($str));
976  }
977 
986  public static function rawUrlEncodeFP($str)
987  {
988  static::logDeprecatedFunction();
989  return str_replace('%2F', '/', rawurlencode($str));
990  }
991 
1010  public static function validEmail($email)
1011  {
1012  // Early return in case input is not a string
1013  if (!is_string($email)) {
1014  return false;
1015  }
1016  $atPosition = strrpos($email, '@');
1017  if (!$atPosition || $atPosition + 1 === strlen($email)) {
1018  // Return if no @ found or it is placed at the very beginning or end of the email
1019  return false;
1020  }
1021  $domain = substr($email, $atPosition + 1);
1022  $user = substr($email, 0, $atPosition);
1023  if (!preg_match('/^[a-z0-9.\\-]*$/i', $domain)) {
1024  $domain = self::idnaEncode($domain);
1025  }
1026  return filter_var($user . '@' . $domain, FILTER_VALIDATE_EMAIL) !== false;
1027  }
1028 
1039  public static function strtoupper($str)
1040  {
1041  self::logDeprecatedFunction();
1042  return strtr((string)$str, 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ');
1043  }
1044 
1055  public static function strtolower($str)
1056  {
1057  self::logDeprecatedFunction();
1058  return strtr((string)$str, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz');
1059  }
1060 
1068  public static function generateRandomBytes($bytesToReturn)
1069  {
1070  self::logDeprecatedFunction();
1071  return self::makeInstance(Random::class)->generateRandomBytes($bytesToReturn);
1072  }
1073 
1080  public static function idnaEncode($value)
1081  {
1082  if (isset(self::$idnaStringCache[$value])) {
1083  return self::$idnaStringCache[$value];
1084  } else {
1085  if (!self::$idnaConverter) {
1086  self::$idnaConverter = new \Mso\IdnaConvert\IdnaConvert(['idn_version' => 2008]);
1087  }
1088  self::$idnaStringCache[$value] = self::$idnaConverter->encode($value);
1089  return self::$idnaStringCache[$value];
1090  }
1091  }
1092 
1100  public static function getRandomHexString($count)
1101  {
1102  self::logDeprecatedFunction();
1103  return self::makeInstance(Random::class)->generateRandomHexString($count);
1104  }
1105 
1113  public static function underscoredToUpperCamelCase($string)
1114  {
1115  return str_replace(' ', '', ucwords(str_replace('_', ' ', strtolower($string))));
1116  }
1117 
1125  public static function underscoredToLowerCamelCase($string)
1126  {
1127  return lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', strtolower($string)))));
1128  }
1129 
1137  public static function camelCaseToLowerCaseUnderscored($string)
1138  {
1139  $value = preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $string);
1140  return mb_strtolower($value, 'utf-8');
1141  }
1142 
1151  public static function lcfirst($string)
1152  {
1153  static::logDeprecatedFunction();
1154  return lcfirst($string);
1155  }
1156 
1181  public static function isValidUrl($url)
1182  {
1183  $parsedUrl = parse_url($url);
1184  if (!$parsedUrl || !isset($parsedUrl['scheme'])) {
1185  return false;
1186  }
1187  // HttpUtility::buildUrl() will always build urls with <scheme>://
1188  // our original $url might only contain <scheme>: (e.g. mail:)
1189  // so we convert that to the double-slashed version to ensure
1190  // our check against the $recomposedUrl is proper
1191  if (!self::isFirstPartOfStr($url, $parsedUrl['scheme'] . '://')) {
1192  $url = str_replace($parsedUrl['scheme'] . ':', $parsedUrl['scheme'] . '://', $url);
1193  }
1194  $recomposedUrl = HttpUtility::buildUrl($parsedUrl);
1195  if ($recomposedUrl !== $url) {
1196  // The parse_url() had to modify characters, so the URL is invalid
1197  return false;
1198  }
1199  if (isset($parsedUrl['host']) && !preg_match('/^[a-z0-9.\\-]*$/i', $parsedUrl['host'])) {
1200  $parsedUrl['host'] = self::idnaEncode($parsedUrl['host']);
1201  }
1202  return filter_var(HttpUtility::buildUrl($parsedUrl), FILTER_VALIDATE_URL) !== false;
1203  }
1204 
1205  /*************************
1206  *
1207  * ARRAY FUNCTIONS
1208  *
1209  *************************/
1210 
1221  public static function intExplode($delimiter, $string, $removeEmptyValues = false, $limit = 0)
1222  {
1223  $result = explode($delimiter, $string);
1224  foreach ($result as $key => &$value) {
1225  if ($removeEmptyValues && ($value === '' || trim($value) === '')) {
1226  unset($result[$key]);
1227  } else {
1228  $value = (int)$value;
1229  }
1230  }
1231  unset($value);
1232  if ($limit !== 0) {
1233  if ($limit < 0) {
1234  $result = array_slice($result, 0, $limit);
1235  } elseif (count($result) > $limit) {
1236  $lastElements = array_slice($result, $limit - 1);
1237  $result = array_slice($result, 0, $limit - 1);
1238  $result[] = implode($delimiter, $lastElements);
1239  }
1240  }
1241  return $result;
1242  }
1243 
1258  public static function revExplode($delimiter, $string, $count = 0)
1259  {
1260  // 2 is the (currently, as of 2014-02) most-used value for $count in the core, therefore we check it first
1261  if ($count === 2) {
1262  $position = strrpos($string, strrev($delimiter));
1263  if ($position !== false) {
1264  return [substr($string, 0, $position), substr($string, $position + strlen($delimiter))];
1265  } else {
1266  return [$string];
1267  }
1268  } elseif ($count <= 1) {
1269  return [$string];
1270  } else {
1271  $explodedValues = explode($delimiter, strrev($string), $count);
1272  $explodedValues = array_map('strrev', $explodedValues);
1273  return array_reverse($explodedValues);
1274  }
1275  }
1276 
1289  public static function trimExplode($delim, $string, $removeEmptyValues = false, $limit = 0)
1290  {
1291  $result = explode($delim, $string);
1292  if ($removeEmptyValues) {
1293  $temp = [];
1294  foreach ($result as $value) {
1295  if (trim($value) !== '') {
1296  $temp[] = $value;
1297  }
1298  }
1299  $result = $temp;
1300  }
1301  if ($limit > 0 && count($result) > $limit) {
1302  $lastElements = array_splice($result, $limit - 1);
1303  $result[] = implode($delim, $lastElements);
1304  } elseif ($limit < 0) {
1305  $result = array_slice($result, 0, $limit);
1306  }
1307  $result = array_map('trim', $result);
1308  return $result;
1309  }
1310 
1322  public static function implodeArrayForUrl($name, array $theArray, $str = '', $skipBlank = false, $rawurlencodeParamName = false)
1323  {
1324  foreach ($theArray as $Akey => $AVal) {
1325  $thisKeyName = $name ? $name . '[' . $Akey . ']' : $Akey;
1326  if (is_array($AVal)) {
1327  $str = self::implodeArrayForUrl($thisKeyName, $AVal, $str, $skipBlank, $rawurlencodeParamName);
1328  } else {
1329  if (!$skipBlank || (string)$AVal !== '') {
1330  $str .= '&' . ($rawurlencodeParamName ? rawurlencode($thisKeyName) : $thisKeyName) . '=' . rawurlencode($AVal);
1331  }
1332  }
1333  }
1334  return $str;
1335  }
1336 
1345  public static function explodeUrl2Array($string, $multidim = false)
1346  {
1347  $output = [];
1348  if ($multidim) {
1349  parse_str($string, $output);
1350  } else {
1351  $p = explode('&', $string);
1352  foreach ($p as $v) {
1353  if ($v !== '') {
1354  list($pK, $pV) = explode('=', $v, 2);
1355  $output[rawurldecode($pK)] = rawurldecode($pV);
1356  }
1357  }
1358  }
1359  return $output;
1360  }
1361 
1371  public static function compileSelectedGetVarsFromArray($varList, array $getArray, $GPvarAlt = true)
1372  {
1373  $keys = self::trimExplode(',', $varList, true);
1374  $outArr = [];
1375  foreach ($keys as $v) {
1376  if (isset($getArray[$v])) {
1377  $outArr[$v] = $getArray[$v];
1378  } elseif ($GPvarAlt) {
1379  $outArr[$v] = self::_GP($v);
1380  }
1381  }
1382  return $outArr;
1383  }
1384 
1393  public static function csvValues(array $row, $delim = ',', $quote = '"')
1394  {
1395  $out = [];
1396  foreach ($row as $value) {
1397  $out[] = str_replace($quote, $quote . $quote, $value);
1398  }
1399  $str = $quote . implode(($quote . $delim . $quote), $out) . $quote;
1400  return $str;
1401  }
1402 
1410  public static function removeDotsFromTS(array $ts)
1411  {
1412  $out = [];
1413  foreach ($ts as $key => $value) {
1414  if (is_array($value)) {
1415  $key = rtrim($key, '.');
1416  $out[$key] = self::removeDotsFromTS($value);
1417  } else {
1418  $out[$key] = $value;
1419  }
1420  }
1421  return $out;
1422  }
1423 
1424  /*************************
1425  *
1426  * HTML/XML PROCESSING
1427  *
1428  *************************/
1437  public static function get_tag_attributes($tag)
1438  {
1439  $components = self::split_tag_attributes($tag);
1440  // Attribute name is stored here
1441  $name = '';
1442  $valuemode = false;
1443  $attributes = [];
1444  foreach ($components as $key => $val) {
1445  // 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
1446  if ($val != '=') {
1447  if ($valuemode) {
1448  if ($name) {
1449  $attributes[$name] = $val;
1450  $name = '';
1451  }
1452  } else {
1453  if ($key = strtolower(preg_replace('/[^[:alnum:]_\\:\\-]/', '', $val))) {
1454  $attributes[$key] = '';
1455  $name = $key;
1456  }
1457  }
1458  $valuemode = false;
1459  } else {
1460  $valuemode = true;
1461  }
1462  }
1463  return $attributes;
1464  }
1465 
1473  public static function split_tag_attributes($tag)
1474  {
1475  $tag_tmp = trim(preg_replace('/^<[^[:space:]]*/', '', trim($tag)));
1476  // Removes any > in the end of the string
1477  $tag_tmp = trim(rtrim($tag_tmp, '>'));
1478  $value = [];
1479  // Compared with empty string instead , 030102
1480  while ($tag_tmp !== '') {
1481  $firstChar = $tag_tmp[0];
1482  if ($firstChar === '"' || $firstChar === '\'') {
1483  $reg = explode($firstChar, $tag_tmp, 3);
1484  $value[] = $reg[1];
1485  $tag_tmp = trim($reg[2]);
1486  } elseif ($firstChar === '=') {
1487  $value[] = '=';
1488  // Removes = chars.
1489  $tag_tmp = trim(substr($tag_tmp, 1));
1490  } else {
1491  // There are '' around the value. We look for the next ' ' or '>'
1492  $reg = preg_split('/[[:space:]=]/', $tag_tmp, 2);
1493  $value[] = trim($reg[0]);
1494  $tag_tmp = trim(substr($tag_tmp, strlen($reg[0]), 1) . $reg[1]);
1495  }
1496  }
1497  reset($value);
1498  return $value;
1499  }
1500 
1509  public static function implodeAttributes(array $arr, $xhtmlSafe = false, $dontOmitBlankAttribs = false)
1510  {
1511  if ($xhtmlSafe) {
1512  $newArr = [];
1513  foreach ($arr as $p => $v) {
1514  if (!isset($newArr[strtolower($p)])) {
1515  $newArr[strtolower($p)] = htmlspecialchars($v);
1516  }
1517  }
1518  $arr = $newArr;
1519  }
1520  $list = [];
1521  foreach ($arr as $p => $v) {
1522  if ((string)$v !== '' || $dontOmitBlankAttribs) {
1523  $list[] = $p . '="' . $v . '"';
1524  }
1525  }
1526  return implode(' ', $list);
1527  }
1528 
1538  public static function wrapJS($string, $_ = null)
1539  {
1540  if ($_ !== null) {
1541  self::deprecationLog('Parameter 2 of GeneralUtility::wrapJS is obsolete and can be omitted.');
1542  }
1543 
1544  if (trim($string)) {
1545  // remove nl from the beginning
1546  $string = ltrim($string, LF);
1547  // re-ident to one tab using the first line as reference
1548  $match = [];
1549  if (preg_match('/^(\\t+)/', $string, $match)) {
1550  $string = str_replace($match[1], TAB, $string);
1551  }
1552  return '<script type="text/javascript">
1553 /*<![CDATA[*/
1554 ' . $string . '
1555 /*]]>*/
1556 </script>';
1557  }
1558  return '';
1559  }
1560 
1569  public static function xml2tree($string, $depth = 999, $parserOptions = [])
1570  {
1571  // Disables the functionality to allow external entities to be loaded when parsing the XML, must be kept
1572  $previousValueOfEntityLoader = libxml_disable_entity_loader(true);
1573  $parser = xml_parser_create();
1574  $vals = [];
1575  $index = [];
1576  xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
1577  xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 0);
1578  foreach ($parserOptions as $option => $value) {
1579  xml_parser_set_option($parser, $option, $value);
1580  }
1581  xml_parse_into_struct($parser, $string, $vals, $index);
1582  libxml_disable_entity_loader($previousValueOfEntityLoader);
1583  if (xml_get_error_code($parser)) {
1584  return 'Line ' . xml_get_current_line_number($parser) . ': ' . xml_error_string(xml_get_error_code($parser));
1585  }
1586  xml_parser_free($parser);
1587  $stack = [[]];
1588  $stacktop = 0;
1589  $startPoint = 0;
1590  $tagi = [];
1591  foreach ($vals as $key => $val) {
1592  $type = $val['type'];
1593  // open tag:
1594  if ($type == 'open' || $type == 'complete') {
1595  $stack[$stacktop++] = $tagi;
1596  if ($depth == $stacktop) {
1597  $startPoint = $key;
1598  }
1599  $tagi = ['tag' => $val['tag']];
1600  if (isset($val['attributes'])) {
1601  $tagi['attrs'] = $val['attributes'];
1602  }
1603  if (isset($val['value'])) {
1604  $tagi['values'][] = $val['value'];
1605  }
1606  }
1607  // finish tag:
1608  if ($type == 'complete' || $type == 'close') {
1609  $oldtagi = $tagi;
1610  $tagi = $stack[--$stacktop];
1611  $oldtag = $oldtagi['tag'];
1612  unset($oldtagi['tag']);
1613  if ($depth == $stacktop + 1) {
1614  if ($key - $startPoint > 0) {
1615  $partArray = array_slice($vals, $startPoint + 1, $key - $startPoint - 1);
1616  $oldtagi['XMLvalue'] = self::xmlRecompileFromStructValArray($partArray);
1617  } else {
1618  $oldtagi['XMLvalue'] = $oldtagi['values'][0];
1619  }
1620  }
1621  $tagi['ch'][$oldtag][] = $oldtagi;
1622  unset($oldtagi);
1623  }
1624  // cdata
1625  if ($type == 'cdata') {
1626  $tagi['values'][] = $val['value'];
1627  }
1628  }
1629  return $tagi['ch'];
1630  }
1631 
1643  public static function array2xml_cs(array $array, $docTag = 'phparray', array $options = [], $charset = '')
1644  {
1645  static::logDeprecatedFunction();
1646  // Set default charset unless explicitly specified
1647  $charset = $charset ?: 'utf-8';
1648  // Return XML:
1649  return '<?xml version="1.0" encoding="' . htmlspecialchars($charset) . '" standalone="yes" ?>' . LF . self::array2xml($array, '', 0, $docTag, 0, $options);
1650  }
1651 
1672  public static function array2xml(array $array, $NSprefix = '', $level = 0, $docTag = 'phparray', $spaceInd = 0, array $options = [], array $stackData = [])
1673  {
1674  // 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
1675  $binaryChars = chr(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);
1676  // Set indenting mode:
1677  $indentChar = $spaceInd ? ' ' : TAB;
1678  $indentN = $spaceInd > 0 ? $spaceInd : 1;
1679  $nl = $spaceInd >= 0 ? LF : '';
1680  // Init output variable:
1681  $output = '';
1682  // Traverse the input array
1683  foreach ($array as $k => $v) {
1684  $attr = '';
1685  $tagName = $k;
1686  // Construct the tag name.
1687  // Use tag based on grand-parent + parent tag name
1688  if (isset($options['grandParentTagMap'][$stackData['grandParentTagName'] . '/' . $stackData['parentTagName']])) {
1689  $attr .= ' index="' . htmlspecialchars($tagName) . '"';
1690  $tagName = (string)$options['grandParentTagMap'][$stackData['grandParentTagName'] . '/' . $stackData['parentTagName']];
1691  } elseif (isset($options['parentTagMap'][$stackData['parentTagName'] . ':_IS_NUM']) && MathUtility::canBeInterpretedAsInteger($tagName)) {
1692  // Use tag based on parent tag name + if current tag is numeric
1693  $attr .= ' index="' . htmlspecialchars($tagName) . '"';
1694  $tagName = (string)$options['parentTagMap'][$stackData['parentTagName'] . ':_IS_NUM'];
1695  } elseif (isset($options['parentTagMap'][$stackData['parentTagName'] . ':' . $tagName])) {
1696  // Use tag based on parent tag name + current tag
1697  $attr .= ' index="' . htmlspecialchars($tagName) . '"';
1698  $tagName = (string)$options['parentTagMap'][$stackData['parentTagName'] . ':' . $tagName];
1699  } elseif (isset($options['parentTagMap'][$stackData['parentTagName']])) {
1700  // Use tag based on parent tag name:
1701  $attr .= ' index="' . htmlspecialchars($tagName) . '"';
1702  $tagName = (string)$options['parentTagMap'][$stackData['parentTagName']];
1703  } elseif (MathUtility::canBeInterpretedAsInteger($tagName)) {
1704  // If integer...;
1705  if ($options['useNindex']) {
1706  // If numeric key, prefix "n"
1707  $tagName = 'n' . $tagName;
1708  } else {
1709  // Use special tag for num. keys:
1710  $attr .= ' index="' . $tagName . '"';
1711  $tagName = $options['useIndexTagForNum'] ?: 'numIndex';
1712  }
1713  } elseif ($options['useIndexTagForAssoc']) {
1714  // Use tag for all associative keys:
1715  $attr .= ' index="' . htmlspecialchars($tagName) . '"';
1716  $tagName = $options['useIndexTagForAssoc'];
1717  }
1718  // The tag name is cleaned up so only alphanumeric chars (plus - and _) are in there and not longer than 100 chars either.
1719  $tagName = substr(preg_replace('/[^[:alnum:]_-]/', '', $tagName), 0, 100);
1720  // If the value is an array then we will call this function recursively:
1721  if (is_array($v)) {
1722  // Sub elements:
1723  if ($options['alt_options'][$stackData['path'] . '/' . $tagName]) {
1724  $subOptions = $options['alt_options'][$stackData['path'] . '/' . $tagName];
1725  $clearStackPath = $subOptions['clearStackPath'];
1726  } else {
1727  $subOptions = $options;
1728  $clearStackPath = false;
1729  }
1730  if (empty($v)) {
1731  $content = '';
1732  } else {
1733  $content = $nl . self::array2xml($v, $NSprefix, ($level + 1), '', $spaceInd, $subOptions, [
1734  'parentTagName' => $tagName,
1735  'grandParentTagName' => $stackData['parentTagName'],
1736  'path' => ($clearStackPath ? '' : $stackData['path'] . '/' . $tagName)
1737  ]) . ($spaceInd >= 0 ? str_pad('', ($level + 1) * $indentN, $indentChar) : '');
1738  }
1739  // Do not set "type = array". Makes prettier XML but means that empty arrays are not restored with xml2array
1740  if ((int)$options['disableTypeAttrib'] != 2) {
1741  $attr .= ' type="array"';
1742  }
1743  } else {
1744  // Just a value:
1745  // Look for binary chars:
1746  $vLen = strlen($v);
1747  // Go for base64 encoding if the initial segment NOT matching any binary char has the same length as the whole string!
1748  if ($vLen && strcspn($v, $binaryChars) != $vLen) {
1749  // If the value contained binary chars then we base64-encode it an set an attribute to notify this situation:
1750  $content = $nl . chunk_split(base64_encode($v));
1751  $attr .= ' base64="1"';
1752  } else {
1753  // Otherwise, just htmlspecialchar the stuff:
1754  $content = htmlspecialchars($v);
1755  $dType = gettype($v);
1756  if ($dType == 'string') {
1757  if ($options['useCDATA'] && $content != $v) {
1758  $content = '<![CDATA[' . $v . ']]>';
1759  }
1760  } elseif (!$options['disableTypeAttrib']) {
1761  $attr .= ' type="' . $dType . '"';
1762  }
1763  }
1764  }
1765  if ((string)$tagName !== '') {
1766  // Add the element to the output string:
1767  $output .= ($spaceInd >= 0 ? str_pad('', ($level + 1) * $indentN, $indentChar) : '')
1768  . '<' . $NSprefix . $tagName . $attr . '>' . $content . '</' . $NSprefix . $tagName . '>' . $nl;
1769  }
1770  }
1771  // If we are at the outer-most level, then we finally wrap it all in the document tags and return that as the value:
1772  if (!$level) {
1773  $output = '<' . $docTag . '>' . $nl . $output . '</' . $docTag . '>';
1774  }
1775  return $output;
1776  }
1777 
1789  public static function xml2array($string, $NSprefix = '', $reportDocTag = false)
1790  {
1791  static $firstLevelCache = [];
1792  $identifier = md5($string . $NSprefix . ($reportDocTag ? '1' : '0'));
1793  // Look up in first level cache
1794  if (!empty($firstLevelCache[$identifier])) {
1795  $array = $firstLevelCache[$identifier];
1796  } else {
1797  $array = self::xml2arrayProcess(trim($string), $NSprefix, $reportDocTag);
1798  // Store content in first level cache
1799  $firstLevelCache[$identifier] = $array;
1800  }
1801  return $array;
1802  }
1803 
1814  protected static function xml2arrayProcess($string, $NSprefix = '', $reportDocTag = false)
1815  {
1816  // Disables the functionality to allow external entities to be loaded when parsing the XML, must be kept
1817  $previousValueOfEntityLoader = libxml_disable_entity_loader(true);
1818  // Create parser:
1819  $parser = xml_parser_create();
1820  $vals = [];
1821  $index = [];
1822  xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
1823  xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 0);
1824  // Default output charset is UTF-8, only ASCII, ISO-8859-1 and UTF-8 are supported!!!
1825  $match = [];
1826  preg_match('/^[[:space:]]*<\\?xml[^>]*encoding[[:space:]]*=[[:space:]]*"([^"]*)"/', substr($string, 0, 200), $match);
1827  $theCharset = $match[1] ?: 'utf-8';
1828  // us-ascii / utf-8 / iso-8859-1
1829  xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $theCharset);
1830  // Parse content:
1831  xml_parse_into_struct($parser, $string, $vals, $index);
1832  libxml_disable_entity_loader($previousValueOfEntityLoader);
1833  // If error, return error message:
1834  if (xml_get_error_code($parser)) {
1835  return 'Line ' . xml_get_current_line_number($parser) . ': ' . xml_error_string(xml_get_error_code($parser));
1836  }
1837  xml_parser_free($parser);
1838  // Init vars:
1839  $stack = [[]];
1840  $stacktop = 0;
1841  $current = [];
1842  $tagName = '';
1843  $documentTag = '';
1844  // Traverse the parsed XML structure:
1845  foreach ($vals as $key => $val) {
1846  // First, process the tag-name (which is used in both cases, whether "complete" or "close")
1847  $tagName = $val['tag'];
1848  if (!$documentTag) {
1849  $documentTag = $tagName;
1850  }
1851  // Test for name space:
1852  $tagName = $NSprefix && substr($tagName, 0, strlen($NSprefix)) == $NSprefix ? substr($tagName, strlen($NSprefix)) : $tagName;
1853  // Test for numeric tag, encoded on the form "nXXX":
1854  $testNtag = substr($tagName, 1);
1855  // Closing tag.
1856  $tagName = $tagName[0] === 'n' && MathUtility::canBeInterpretedAsInteger($testNtag) ? (int)$testNtag : $tagName;
1857  // Test for alternative index value:
1858  if ((string)$val['attributes']['index'] !== '') {
1859  $tagName = $val['attributes']['index'];
1860  }
1861  // Setting tag-values, manage stack:
1862  switch ($val['type']) {
1863  case 'open':
1864  // If open tag it means there is an array stored in sub-elements. Therefore increase the stackpointer and reset the accumulation array:
1865  // Setting blank place holder
1866  $current[$tagName] = [];
1867  $stack[$stacktop++] = $current;
1868  $current = [];
1869  break;
1870  case 'close':
1871  // If the tag is "close" then it is an array which is closing and we decrease the stack pointer.
1872  $oldCurrent = $current;
1873  $current = $stack[--$stacktop];
1874  // Going to the end of array to get placeholder key, key($current), and fill in array next:
1875  end($current);
1876  $current[key($current)] = $oldCurrent;
1877  unset($oldCurrent);
1878  break;
1879  case 'complete':
1880  // If "complete", then it's a value. If the attribute "base64" is set, then decode the value, otherwise just set it.
1881  if ($val['attributes']['base64']) {
1882  $current[$tagName] = base64_decode($val['value']);
1883  } else {
1884  // Had to cast it as a string - otherwise it would be evaluate FALSE if tested with isset()!!
1885  $current[$tagName] = (string)$val['value'];
1886  // Cast type:
1887  switch ((string)$val['attributes']['type']) {
1888  case 'integer':
1889  $current[$tagName] = (int)$current[$tagName];
1890  break;
1891  case 'double':
1892  $current[$tagName] = (double) $current[$tagName];
1893  break;
1894  case 'boolean':
1895  $current[$tagName] = (bool)$current[$tagName];
1896  break;
1897  case 'NULL':
1898  $current[$tagName] = null;
1899  break;
1900  case 'array':
1901  // 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...
1902  $current[$tagName] = [];
1903  break;
1904  }
1905  }
1906  break;
1907  }
1908  }
1909  if ($reportDocTag) {
1910  $current[$tagName]['_DOCUMENT_TAG'] = $documentTag;
1911  }
1912  // Finally return the content of the document tag.
1913  return $current[$tagName];
1914  }
1915 
1922  public static function xmlRecompileFromStructValArray(array $vals)
1923  {
1924  $XMLcontent = '';
1925  foreach ($vals as $val) {
1926  $type = $val['type'];
1927  // Open tag:
1928  if ($type == 'open' || $type == 'complete') {
1929  $XMLcontent .= '<' . $val['tag'];
1930  if (isset($val['attributes'])) {
1931  foreach ($val['attributes'] as $k => $v) {
1932  $XMLcontent .= ' ' . $k . '="' . htmlspecialchars($v) . '"';
1933  }
1934  }
1935  if ($type == 'complete') {
1936  if (isset($val['value'])) {
1937  $XMLcontent .= '>' . htmlspecialchars($val['value']) . '</' . $val['tag'] . '>';
1938  } else {
1939  $XMLcontent .= '/>';
1940  }
1941  } else {
1942  $XMLcontent .= '>';
1943  }
1944  if ($type == 'open' && isset($val['value'])) {
1945  $XMLcontent .= htmlspecialchars($val['value']);
1946  }
1947  }
1948  // Finish tag:
1949  if ($type == 'close') {
1950  $XMLcontent .= '</' . $val['tag'] . '>';
1951  }
1952  // Cdata
1953  if ($type == 'cdata') {
1954  $XMLcontent .= htmlspecialchars($val['value']);
1955  }
1956  }
1957  return $XMLcontent;
1958  }
1959 
1967  public static function xmlGetHeaderAttribs($xmlData)
1968  {
1969  self::logDeprecatedFunction();
1970  $match = [];
1971  if (preg_match('/^\\s*<\\?xml([^>]*)\\?\\>/', $xmlData, $match)) {
1972  return self::get_tag_attributes($match[1]);
1973  }
1974  }
1975 
1983  public static function minifyJavaScript($script, &$error = '')
1984  {
1985  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_div.php']['minifyJavaScript'])) {
1986  $fakeThis = false;
1987  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_div.php']['minifyJavaScript'] as $hookMethod) {
1988  try {
1989  $parameters = ['script' => $script];
1990  $script = static::callUserFunction($hookMethod, $parameters, $fakeThis);
1991  } catch (\Exception $e) {
1992  $errorMessage = 'Error minifying java script: ' . $e->getMessage();
1993  $error .= $errorMessage;
1994  static::devLog($errorMessage, \TYPO3\CMS\Core\Utility\GeneralUtility::class, 2, [
1995  'JavaScript' => $script,
1996  'Stack trace' => $e->getTrace(),
1997  'hook' => $hookMethod
1998  ]);
1999  }
2000  }
2001  }
2002  return $script;
2003  }
2004 
2005  /*************************
2006  *
2007  * FILES FUNCTIONS
2008  *
2009  *************************/
2020  public static function getUrl($url, $includeHeader = 0, $requestHeaders = null, &$report = null)
2021  {
2022  if (isset($report)) {
2023  $report['error'] = 0;
2024  $report['message'] = '';
2025  }
2026  // Looks like it's an external file, use Guzzle by default
2027  if (preg_match('/^(?:http|ftp)s?|s(?:ftp|cp):/', $url)) {
2029  $requestFactory = static::makeInstance(RequestFactory::class);
2030  if (is_array($requestHeaders)) {
2031  $configuration = ['headers' => $requestHeaders];
2032  } else {
2033  $configuration = [];
2034  }
2035 
2036  try {
2037  if (isset($report)) {
2038  $report['lib'] = 'GuzzleHttp';
2039  }
2040  $response = $requestFactory->request($url, 'GET', $configuration);
2041  } catch (RequestException $exception) {
2042  if (isset($report)) {
2043  $report['error'] = $exception->getHandlerContext()['errno'];
2044  $report['message'] = $exception->getMessage();
2045  $report['exception'] = $exception;
2046  }
2047  return false;
2048  }
2049 
2050  $content = '';
2051 
2052  // Add the headers to the output
2053  $includeHeader = (int)$includeHeader;
2054  if ($includeHeader) {
2055  $parsedURL = parse_url($url);
2056  $method = $includeHeader === 2 ? 'HEAD' : 'GET';
2057  $content = $method . ' ' . (isset($parsedURL['path']) ? $parsedURL['path'] : '/')
2058  . ($parsedURL['query'] ? '?' . $parsedURL['query'] : '') . ' HTTP/1.0' . CRLF
2059  . 'Host: ' . $parsedURL['host'] . CRLF
2060  . 'Connection: close' . CRLF;
2061  if (is_array($requestHeaders)) {
2062  $content .= implode(CRLF, $requestHeaders) . CRLF;
2063  }
2064  foreach ($response->getHeaders() as $headerName => $headerValues) {
2065  $content .= $headerName . ': ' . implode(', ', $headerValues) . CRLF;
2066  }
2067  // Headers are separated from the body with two CRLFs
2068  $content .= CRLF;
2069  }
2070  // If not just headers are requested, add the body
2071  if ($includeHeader !== 2) {
2072  $content .= $response->getBody()->getContents();
2073  }
2074  if (isset($report)) {
2075  $report['lib'] = 'http';
2076  if ($response->getStatusCode() >= 300 && $response->getStatusCode() < 400) {
2077  $report['http_code'] = $response->getStatusCode();
2078  $report['content_type'] = $response->getHeader('Content-Type');
2079  $report['error'] = $response->getStatusCode();
2080  $report['message'] = $response->getReasonPhrase();
2081  } elseif (!empty($content)) {
2082  $report['error'] = $response->getStatusCode();
2083  $report['message'] = $response->getReasonPhrase();
2084  } elseif ($includeHeader) {
2085  // Set only for $includeHeader to work exactly like PHP variant
2086  $report['http_code'] = $response->getStatusCode();
2087  $report['content_type'] = $response->getHeader('Content-Type');
2088  }
2089  }
2090  } else {
2091  if (isset($report)) {
2092  $report['lib'] = 'file';
2093  }
2094  $content = @file_get_contents($url);
2095  if ($content === false && isset($report)) {
2096  $report['error'] = -1;
2097  $report['message'] = 'Couldn\'t get URL: ' . $url;
2098  }
2099  }
2100  return $content;
2101  }
2102 
2111  public static function writeFile($file, $content, $changePermissions = false)
2112  {
2113  if (!@is_file($file)) {
2114  $changePermissions = true;
2115  }
2116  if ($fd = fopen($file, 'wb')) {
2117  $res = fwrite($fd, $content);
2118  fclose($fd);
2119  if ($res === false) {
2120  return false;
2121  }
2122  // Change the permissions only if the file has just been created
2123  if ($changePermissions) {
2124  static::fixPermissions($file);
2125  }
2126  return true;
2127  }
2128  return false;
2129  }
2130 
2138  public static function fixPermissions($path, $recursive = false)
2139  {
2140  if (TYPO3_OS === 'WIN') {
2141  return true;
2142  }
2143  $result = false;
2144  // Make path absolute
2145  if (!static::isAbsPath($path)) {
2146  $path = static::getFileAbsFileName($path);
2147  }
2148  if (static::isAllowedAbsPath($path)) {
2149  if (@is_file($path)) {
2150  $targetPermissions = isset($GLOBALS['TYPO3_CONF_VARS']['SYS']['fileCreateMask'])
2151  ? $GLOBALS['TYPO3_CONF_VARS']['SYS']['fileCreateMask']
2152  : '0644';
2153  } elseif (@is_dir($path)) {
2154  $targetPermissions = isset($GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask'])
2155  ? $GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask']
2156  : '0755';
2157  }
2158  if (!empty($targetPermissions)) {
2159  // make sure it's always 4 digits
2160  $targetPermissions = str_pad($targetPermissions, 4, 0, STR_PAD_LEFT);
2161  $targetPermissions = octdec($targetPermissions);
2162  // "@" is there because file is not necessarily OWNED by the user
2163  $result = @chmod($path, $targetPermissions);
2164  }
2165  // Set createGroup if not empty
2166  if (
2167  isset($GLOBALS['TYPO3_CONF_VARS']['SYS']['createGroup'])
2168  && $GLOBALS['TYPO3_CONF_VARS']['SYS']['createGroup'] !== ''
2169  ) {
2170  // "@" is there because file is not necessarily OWNED by the user
2171  $changeGroupResult = @chgrp($path, $GLOBALS['TYPO3_CONF_VARS']['SYS']['createGroup']);
2172  $result = $changeGroupResult ? $result : false;
2173  }
2174  // Call recursive if recursive flag if set and $path is directory
2175  if ($recursive && @is_dir($path)) {
2176  $handle = opendir($path);
2177  if (is_resource($handle)) {
2178  while (($file = readdir($handle)) !== false) {
2179  $recursionResult = null;
2180  if ($file !== '.' && $file !== '..') {
2181  if (@is_file(($path . '/' . $file))) {
2182  $recursionResult = static::fixPermissions($path . '/' . $file);
2183  } elseif (@is_dir(($path . '/' . $file))) {
2184  $recursionResult = static::fixPermissions($path . '/' . $file, true);
2185  }
2186  if (isset($recursionResult) && !$recursionResult) {
2187  $result = false;
2188  }
2189  }
2190  }
2191  closedir($handle);
2192  }
2193  }
2194  }
2195  return $result;
2196  }
2197 
2206  public static function writeFileToTypo3tempDir($filepath, $content)
2207  {
2208  if (!defined('PATH_site')) {
2209  return 'PATH_site constant was NOT defined!';
2210  }
2211 
2212  // Parse filepath into directory and basename:
2213  $fI = pathinfo($filepath);
2214  $fI['dirname'] .= '/';
2215  // Check parts:
2216  if (!static::validPathStr($filepath) || !$fI['basename'] || strlen($fI['basename']) >= 60) {
2217  return 'Input filepath "' . $filepath . '" was generally invalid!';
2218  }
2219  // Setting main temporary directory name (standard)
2220  $dirName = PATH_site . 'typo3temp/';
2221  if (!@is_dir($dirName)) {
2222  return 'PATH_site + "typo3temp/" was not a directory!';
2223  }
2224  if (!static::isFirstPartOfStr($fI['dirname'], $dirName)) {
2225  return '"' . $fI['dirname'] . '" was not within directory PATH_site + "typo3temp/"';
2226  }
2227  // Checking if the "subdir" is found:
2228  $subdir = substr($fI['dirname'], strlen($dirName));
2229  if ($subdir) {
2230  if (preg_match('#^(?:[[:alnum:]_]+/)+$#', $subdir)) {
2231  $dirName .= $subdir;
2232  if (!@is_dir($dirName)) {
2233  static::mkdir_deep(PATH_site . 'typo3temp/', $subdir);
2234  }
2235  } else {
2236  return 'Subdir, "' . $subdir . '", was NOT on the form "[[:alnum:]_]/+"';
2237  }
2238  }
2239  // Checking dir-name again (sub-dir might have been created):
2240  if (@is_dir($dirName)) {
2241  if ($filepath == $dirName . $fI['basename']) {
2242  static::writeFile($filepath, $content);
2243  if (!@is_file($filepath)) {
2244  return 'The file was not written to the disk. Please, check that you have write permissions to the typo3temp/ directory.';
2245  }
2246  } else {
2247  return 'Calculated filelocation didn\'t match input "' . $filepath . '".';
2248  }
2249  } else {
2250  return '"' . $dirName . '" is not a directory!';
2251  }
2252  return null;
2253  }
2254 
2263  public static function mkdir($newFolder)
2264  {
2265  $result = @mkdir($newFolder, octdec($GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask']));
2266  if ($result) {
2267  static::fixPermissions($newFolder);
2268  }
2269  return $result;
2270  }
2271 
2282  public static function mkdir_deep($directory, $deepDirectory = '')
2283  {
2284  if (!is_string($directory)) {
2285  throw new \InvalidArgumentException('The specified directory is of type "' . gettype($directory) . '" but a string is expected.', 1303662955);
2286  }
2287  if (!is_string($deepDirectory)) {
2288  throw new \InvalidArgumentException('The specified directory is of type "' . gettype($deepDirectory) . '" but a string is expected.', 1303662956);
2289  }
2290  // Ensure there is only one slash
2291  $fullPath = rtrim($directory, '/') . '/' . ltrim($deepDirectory, '/');
2292  if ($fullPath !== '' && !is_dir($fullPath)) {
2293  $firstCreatedPath = static::createDirectoryPath($fullPath);
2294  if ($firstCreatedPath !== '') {
2295  static::fixPermissions($firstCreatedPath, true);
2296  }
2297  }
2298  }
2299 
2311  protected static function createDirectoryPath($fullDirectoryPath)
2312  {
2313  $currentPath = $fullDirectoryPath;
2314  $firstCreatedPath = '';
2315  $permissionMask = octdec($GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask']);
2316  if (!@is_dir($currentPath)) {
2317  do {
2318  $firstCreatedPath = $currentPath;
2319  $separatorPosition = strrpos($currentPath, DIRECTORY_SEPARATOR);
2320  $currentPath = substr($currentPath, 0, $separatorPosition);
2321  } while (!is_dir($currentPath) && $separatorPosition !== false);
2322  $result = @mkdir($fullDirectoryPath, $permissionMask, true);
2323  // Check existence of directory again to avoid race condition. Directory could have get created by another process between previous is_dir() and mkdir()
2324  if (!$result && !@is_dir($fullDirectoryPath)) {
2325  throw new \RuntimeException('Could not create directory "' . $fullDirectoryPath . '"!', 1170251401);
2326  }
2327  }
2328  return $firstCreatedPath;
2329  }
2330 
2338  public static function rmdir($path, $removeNonEmpty = false)
2339  {
2340  $OK = false;
2341  // Remove trailing slash
2342  $path = preg_replace('|/$|', '', $path);
2343  if (file_exists($path)) {
2344  $OK = true;
2345  if (!is_link($path) && is_dir($path)) {
2346  if ($removeNonEmpty == true && ($handle = @opendir($path))) {
2347  while ($OK && false !== ($file = readdir($handle))) {
2348  if ($file == '.' || $file == '..') {
2349  continue;
2350  }
2351  $OK = static::rmdir($path . '/' . $file, $removeNonEmpty);
2352  }
2353  closedir($handle);
2354  }
2355  if ($OK) {
2356  $OK = @rmdir($path);
2357  }
2358  } elseif (is_link($path) && is_dir($path) && TYPO3_OS === 'WIN') {
2359  $OK = @rmdir($path);
2360  } else {
2361  // If $path is a file, simply remove it
2362  $OK = @unlink($path);
2363  }
2364  clearstatcache();
2365  } elseif (is_link($path)) {
2366  $OK = @unlink($path);
2367  if (!$OK && TYPO3_OS === 'WIN') {
2368  // Try to delete dead folder links on Windows systems
2369  $OK = @rmdir($path);
2370  }
2371  clearstatcache();
2372  }
2373  return $OK;
2374  }
2375 
2386  public static function flushDirectory($directory, $keepOriginalDirectory = false, $flushOpcodeCache = false)
2387  {
2388  $result = false;
2389 
2390  if (is_dir($directory)) {
2391  $temporaryDirectory = rtrim($directory, '/') . '.' . StringUtility::getUniqueId('remove') . '/';
2392  if (rename($directory, $temporaryDirectory)) {
2393  if ($flushOpcodeCache) {
2394  self::makeInstance(OpcodeCacheService::class)->clearAllActive($directory);
2395  }
2396  if ($keepOriginalDirectory) {
2397  static::mkdir($directory);
2398  }
2399  clearstatcache();
2400  $result = static::rmdir($temporaryDirectory, true);
2401  }
2402  }
2403 
2404  return $result;
2405  }
2406 
2414  public static function get_dirs($path)
2415  {
2416  if ($path) {
2417  if (is_dir($path)) {
2418  $dir = scandir($path);
2419  $dirs = [];
2420  foreach ($dir as $entry) {
2421  if (is_dir($path . '/' . $entry) && $entry != '..' && $entry != '.') {
2422  $dirs[] = $entry;
2423  }
2424  }
2425  } else {
2426  $dirs = 'error';
2427  }
2428  }
2429  return $dirs;
2430  }
2431 
2444  public static function getFilesInDir($path, $extensionList = '', $prependPath = false, $order = '', $excludePattern = '')
2445  {
2446  $excludePattern = (string)$excludePattern;
2447  $path = rtrim($path, '/');
2448  if (!@is_dir($path)) {
2449  return [];
2450  }
2451 
2452  $rawFileList = scandir($path);
2453  if ($rawFileList === false) {
2454  return 'error opening path: "' . $path . '"';
2455  }
2456 
2457  $pathPrefix = $path . '/';
2458  $extensionList = ',' . $extensionList . ',';
2459  $files = [];
2460  foreach ($rawFileList as $entry) {
2461  $completePathToEntry = $pathPrefix . $entry;
2462  if (!@is_file($completePathToEntry)) {
2463  continue;
2464  }
2465 
2466  if (
2467  ($extensionList === ',,' || stripos($extensionList, ',' . pathinfo($entry, PATHINFO_EXTENSION) . ',') !== false)
2468  && ($excludePattern === '' || !preg_match(('/^' . $excludePattern . '$/'), $entry))
2469  ) {
2470  if ($order !== 'mtime') {
2471  $files[] = $entry;
2472  } else {
2473  // Store the value in the key so we can do a fast asort later.
2474  $files[$entry] = filemtime($completePathToEntry);
2475  }
2476  }
2477  }
2478 
2479  $valueName = 'value';
2480  if ($order === 'mtime') {
2481  asort($files);
2482  $valueName = 'key';
2483  }
2484 
2485  $valuePathPrefix = $prependPath ? $pathPrefix : '';
2486  $foundFiles = [];
2487  foreach ($files as $key => $value) {
2488  // Don't change this ever - extensions may depend on the fact that the hash is an md5 of the path! (import/export extension)
2489  $foundFiles[md5($pathPrefix . ${$valueName})] = $valuePathPrefix . ${$valueName};
2490  }
2491 
2492  return $foundFiles;
2493  }
2494 
2506  public static function getAllFilesAndFoldersInPath(array $fileArr, $path, $extList = '', $regDirs = false, $recursivityLevels = 99, $excludePattern = '')
2507  {
2508  if ($regDirs) {
2509  $fileArr[md5($path)] = $path;
2510  }
2511  $fileArr = array_merge($fileArr, self::getFilesInDir($path, $extList, 1, 1, $excludePattern));
2512  $dirs = self::get_dirs($path);
2513  if ($recursivityLevels > 0 && is_array($dirs)) {
2514  foreach ($dirs as $subdirs) {
2515  if ((string)$subdirs !== '' && ($excludePattern === '' || !preg_match(('/^' . $excludePattern . '$/'), $subdirs))) {
2516  $fileArr = self::getAllFilesAndFoldersInPath($fileArr, $path . $subdirs . '/', $extList, $regDirs, $recursivityLevels - 1, $excludePattern);
2517  }
2518  }
2519  }
2520  return $fileArr;
2521  }
2522 
2530  public static function removePrefixPathFromList(array $fileArr, $prefixToRemove)
2531  {
2532  foreach ($fileArr as $k => &$absFileRef) {
2533  if (self::isFirstPartOfStr($absFileRef, $prefixToRemove)) {
2534  $absFileRef = substr($absFileRef, strlen($prefixToRemove));
2535  } else {
2536  return 'ERROR: One or more of the files was NOT prefixed with the prefix-path!';
2537  }
2538  }
2539  unset($absFileRef);
2540  return $fileArr;
2541  }
2542 
2549  public static function fixWindowsFilePath($theFile)
2550  {
2551  return str_replace(['\\', '//'], '/', $theFile);
2552  }
2553 
2561  public static function resolveBackPath($pathStr)
2562  {
2563  if (strpos($pathStr, '..') === false) {
2564  return $pathStr;
2565  }
2566  $parts = explode('/', $pathStr);
2567  $output = [];
2568  $c = 0;
2569  foreach ($parts as $part) {
2570  if ($part === '..') {
2571  if ($c) {
2572  array_pop($output);
2573  --$c;
2574  } else {
2575  $output[] = $part;
2576  }
2577  } else {
2578  ++$c;
2579  $output[] = $part;
2580  }
2581  }
2582  return implode('/', $output);
2583  }
2584 
2594  public static function locationHeaderUrl($path)
2595  {
2596  $uI = parse_url($path);
2597  // relative to HOST
2598  if ($path[0] === '/') {
2599  $path = self::getIndpEnv('TYPO3_REQUEST_HOST') . $path;
2600  } elseif (!$uI['scheme']) {
2601  // No scheme either
2602  $path = self::getIndpEnv('TYPO3_REQUEST_DIR') . $path;
2603  }
2604  return $path;
2605  }
2606 
2614  public static function getMaxUploadFileSize()
2615  {
2616  // Check for PHP restrictions of the maximum size of one of the $_FILES
2617  $phpUploadLimit = self::getBytesFromSizeMeasurement(ini_get('upload_max_filesize'));
2618  // Check for PHP restrictions of the maximum $_POST size
2619  $phpPostLimit = self::getBytesFromSizeMeasurement(ini_get('post_max_size'));
2620  // If the total amount of post data is smaller (!) than the upload_max_filesize directive,
2621  // then this is the real limit in PHP
2622  $phpUploadLimit = $phpPostLimit > 0 && $phpPostLimit < $phpUploadLimit ? $phpPostLimit : $phpUploadLimit;
2623  return floor(($phpUploadLimit)) / 1024;
2624  }
2625 
2632  public static function getBytesFromSizeMeasurement($measurement)
2633  {
2634  $bytes = (float)$measurement;
2635  if (stripos($measurement, 'G')) {
2636  $bytes *= 1024 * 1024 * 1024;
2637  } elseif (stripos($measurement, 'M')) {
2638  $bytes *= 1024 * 1024;
2639  } elseif (stripos($measurement, 'K')) {
2640  $bytes *= 1024;
2641  }
2642  return $bytes;
2643  }
2644 
2651  public static function getMaximumPathLength()
2652  {
2653  static::logDeprecatedFunction();
2654  return PHP_MAXPATHLEN;
2655  }
2656 
2673  public static function createVersionNumberedFilename($file)
2674  {
2675  $lookupFile = explode('?', $file);
2676  $path = self::resolveBackPath(self::dirname(PATH_thisScript) . '/' . $lookupFile[0]);
2677 
2678  $doNothing = false;
2679  if (TYPO3_MODE == 'FE') {
2680  $mode = strtolower($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['versionNumberInFilename']);
2681  if ($mode === 'embed') {
2682  $mode = true;
2683  } else {
2684  if ($mode === 'querystring') {
2685  $mode = false;
2686  } else {
2687  $doNothing = true;
2688  }
2689  }
2690  } else {
2691  $mode = $GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['versionNumberInFilename'];
2692  }
2693  if (!file_exists($path) || $doNothing) {
2694  // File not found, return filename unaltered
2695  $fullName = $file;
2696  } else {
2697  if (!$mode) {
2698  // If use of .htaccess rule is not configured,
2699  // we use the default query-string method
2700  if ($lookupFile[1]) {
2701  $separator = '&';
2702  } else {
2703  $separator = '?';
2704  }
2705  $fullName = $file . $separator . filemtime($path);
2706  } else {
2707  // Change the filename
2708  $name = explode('.', $lookupFile[0]);
2709  $extension = array_pop($name);
2710  array_push($name, filemtime($path), $extension);
2711  $fullName = implode('.', $name);
2712  // Append potential query string
2713  $fullName .= $lookupFile[1] ? '?' . $lookupFile[1] : '';
2714  }
2715  }
2716  return $fullName;
2717  }
2718 
2719  /*************************
2720  *
2721  * SYSTEM INFORMATION
2722  *
2723  *************************/
2724 
2733  public static function linkThisScript(array $getParams = [])
2734  {
2735  $parts = self::getIndpEnv('SCRIPT_NAME');
2736  $params = self::_GET();
2737  foreach ($getParams as $key => $value) {
2738  if ($value !== '') {
2739  $params[$key] = $value;
2740  } else {
2741  unset($params[$key]);
2742  }
2743  }
2744  $pString = self::implodeArrayForUrl('', $params);
2745  return $pString ? $parts . '?' . ltrim($pString, '&') : $parts;
2746  }
2747 
2756  public static function linkThisUrl($url, array $getParams = [])
2757  {
2758  $parts = parse_url($url);
2759  $getP = [];
2760  if ($parts['query']) {
2761  parse_str($parts['query'], $getP);
2762  }
2763  ArrayUtility::mergeRecursiveWithOverrule($getP, $getParams);
2764  $uP = explode('?', $url);
2765  $params = self::implodeArrayForUrl('', $getP);
2766  $outurl = $uP[0] . ($params ? '?' . substr($params, 1) : '');
2767  return $outurl;
2768  }
2769 
2778  public static function getIndpEnv($getEnvName)
2779  {
2780  if (isset(self::$indpEnvCache[$getEnvName])) {
2781  return self::$indpEnvCache[$getEnvName];
2782  }
2783 
2784  /*
2785  Conventions:
2786  output from parse_url():
2787  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
2788  [scheme] => 'http'
2789  [user] => 'username'
2790  [pass] => 'password'
2791  [host] => '192.168.1.4'
2792  [port] => '8080'
2793  [path] => '/typo3/32/temp/phpcheck/index.php/arg1/arg2/arg3/'
2794  [query] => 'arg1,arg2,arg3&p1=parameter1&p2[key]=value'
2795  [fragment] => 'link1'Further definition: [path_script] = '/typo3/32/temp/phpcheck/index.php'
2796  [path_dir] = '/typo3/32/temp/phpcheck/'
2797  [path_info] = '/arg1/arg2/arg3/'
2798  [path] = [path_script/path_dir][path_info]Keys supported:URI______:
2799  REQUEST_URI = [path]?[query] = /typo3/32/temp/phpcheck/index.php/arg1/arg2/arg3/?arg1,arg2,arg3&p1=parameter1&p2[key]=value
2800  HTTP_HOST = [host][:[port]] = 192.168.1.4:8080
2801  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')!
2802  PATH_INFO = [path_info] = /arg1/arg2/arg3/
2803  QUERY_STRING = [query] = arg1,arg2,arg3&p1=parameter1&p2[key]=value
2804  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
2805  (Notice: NO username/password + NO fragment)CLIENT____:
2806  REMOTE_ADDR = (client IP)
2807  REMOTE_HOST = (client host)
2808  HTTP_USER_AGENT = (client user agent)
2809  HTTP_ACCEPT_LANGUAGE = (client accept language)SERVER____:
2810  SCRIPT_FILENAME = Absolute filename of script (Differs between windows/unix). On windows 'C:\\blabla\\blabl\\' will be converted to 'C:/blabla/blabl/'Special extras:
2811  TYPO3_HOST_ONLY = [host] = 192.168.1.4
2812  TYPO3_PORT = [port] = 8080 (blank if 80, taken from host value)
2813  TYPO3_REQUEST_HOST = [scheme]://[host][:[port]]
2814  TYPO3_REQUEST_URL = [scheme]://[host][:[port]][path]?[query] (scheme will by default be "http" until we can detect something different)
2815  TYPO3_REQUEST_SCRIPT = [scheme]://[host][:[port]][path_script]
2816  TYPO3_REQUEST_DIR = [scheme]://[host][:[port]][path_dir]
2817  TYPO3_SITE_URL = [scheme]://[host][:[port]][path_dir] of the TYPO3 website frontend
2818  TYPO3_SITE_PATH = [path_dir] of the TYPO3 website frontend
2819  TYPO3_SITE_SCRIPT = [script / Speaking URL] of the TYPO3 website
2820  TYPO3_DOCUMENT_ROOT = Absolute path of root of documents: TYPO3_DOCUMENT_ROOT.SCRIPT_NAME = SCRIPT_FILENAME (typically)
2821  TYPO3_SSL = Returns TRUE if this session uses SSL/TLS (https)
2822  TYPO3_PROXY = Returns TRUE if this session runs over a well known proxyNotice: [fragment] is apparently NEVER available to the script!Testing suggestions:
2823  - Output all the values.
2824  - 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
2825  - ALSO TRY the script from the ROOT of a site (like 'http://www.mytest.com/' and not 'http://www.mytest.com/test/' !!)
2826  */
2827  $retVal = '';
2828  switch ((string)$getEnvName) {
2829  case 'SCRIPT_NAME':
2830  $retVal = self::isRunningOnCgiServerApi()
2831  && ($_SERVER['ORIG_PATH_INFO'] ?: $_SERVER['PATH_INFO'])
2832  ? ($_SERVER['ORIG_PATH_INFO'] ?: $_SERVER['PATH_INFO'])
2833  : ($_SERVER['ORIG_SCRIPT_NAME'] ?: $_SERVER['SCRIPT_NAME']);
2834  // Add a prefix if TYPO3 is behind a proxy: ext-domain.com => int-server.com/prefix
2835  if (self::cmpIP($_SERVER['REMOTE_ADDR'], $GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyIP'])) {
2836  if (self::getIndpEnv('TYPO3_SSL') && $GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyPrefixSSL']) {
2837  $retVal = $GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyPrefixSSL'] . $retVal;
2838  } elseif ($GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyPrefix']) {
2839  $retVal = $GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyPrefix'] . $retVal;
2840  }
2841  }
2842  break;
2843  case 'SCRIPT_FILENAME':
2844  $retVal = PATH_thisScript;
2845  break;
2846  case 'REQUEST_URI':
2847  // Typical application of REQUEST_URI is return urls, forms submitting to itself etc. Example: returnUrl='.rawurlencode(\TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('REQUEST_URI'))
2848  if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['requestURIvar']) {
2849  // This is for URL rewriters that store the original URI in a server variable (eg ISAPI_Rewriter for IIS: HTTP_X_REWRITE_URL)
2850  list($v, $n) = explode('|', $GLOBALS['TYPO3_CONF_VARS']['SYS']['requestURIvar']);
2851  $retVal = $GLOBALS[$v][$n];
2852  } elseif (!$_SERVER['REQUEST_URI']) {
2853  // This is for ISS/CGI which does not have the REQUEST_URI available.
2854  $retVal = '/' . ltrim(self::getIndpEnv('SCRIPT_NAME'), '/') . ($_SERVER['QUERY_STRING'] ? '?' . $_SERVER['QUERY_STRING'] : '');
2855  } else {
2856  $retVal = '/' . ltrim($_SERVER['REQUEST_URI'], '/');
2857  }
2858  // Add a prefix if TYPO3 is behind a proxy: ext-domain.com => int-server.com/prefix
2859  if (self::cmpIP($_SERVER['REMOTE_ADDR'], $GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyIP'])) {
2860  if (self::getIndpEnv('TYPO3_SSL') && $GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyPrefixSSL']) {
2861  $retVal = $GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyPrefixSSL'] . $retVal;
2862  } elseif ($GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyPrefix']) {
2863  $retVal = $GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyPrefix'] . $retVal;
2864  }
2865  }
2866  break;
2867  case 'PATH_INFO':
2868  // $_SERVER['PATH_INFO'] != $_SERVER['SCRIPT_NAME'] is necessary because some servers (Windows/CGI)
2869  // are seen to set PATH_INFO equal to script_name
2870  // Further, there must be at least one '/' in the path - else the PATH_INFO value does not make sense.
2871  // IF 'PATH_INFO' never works for our purpose in TYPO3 with CGI-servers,
2872  // then 'PHP_SAPI=='cgi'' might be a better check.
2873  // Right now strcmp($_SERVER['PATH_INFO'], GeneralUtility::getIndpEnv('SCRIPT_NAME')) will always
2874  // return FALSE for CGI-versions, but that is only as long as SCRIPT_NAME is set equal to PATH_INFO
2875  // because of PHP_SAPI=='cgi' (see above)
2876  if (!self::isRunningOnCgiServerApi()) {
2877  $retVal = $_SERVER['PATH_INFO'];
2878  }
2879  break;
2880  case 'TYPO3_REV_PROXY':
2881  $retVal = self::cmpIP($_SERVER['REMOTE_ADDR'], $GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyIP']);
2882  break;
2883  case 'REMOTE_ADDR':
2884  $retVal = $_SERVER['REMOTE_ADDR'];
2885  if (self::cmpIP($_SERVER['REMOTE_ADDR'], $GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyIP'])) {
2886  $ip = self::trimExplode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
2887  // Choose which IP in list to use
2888  if (!empty($ip)) {
2889  switch ($GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyHeaderMultiValue']) {
2890  case 'last':
2891  $ip = array_pop($ip);
2892  break;
2893  case 'first':
2894  $ip = array_shift($ip);
2895  break;
2896  case 'none':
2897 
2898  default:
2899  $ip = '';
2900  }
2901  }
2902  if (self::validIP($ip)) {
2903  $retVal = $ip;
2904  }
2905  }
2906  break;
2907  case 'HTTP_HOST':
2908  // if it is not set we're most likely on the cli
2909  $retVal = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : null;
2910  if (isset($_SERVER['REMOTE_ADDR']) && static::cmpIP($_SERVER['REMOTE_ADDR'], $GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyIP'])) {
2911  $host = self::trimExplode(',', $_SERVER['HTTP_X_FORWARDED_HOST']);
2912  // Choose which host in list to use
2913  if (!empty($host)) {
2914  switch ($GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyHeaderMultiValue']) {
2915  case 'last':
2916  $host = array_pop($host);
2917  break;
2918  case 'first':
2919  $host = array_shift($host);
2920  break;
2921  case 'none':
2922 
2923  default:
2924  $host = '';
2925  }
2926  }
2927  if ($host) {
2928  $retVal = $host;
2929  }
2930  }
2931  if (!static::isAllowedHostHeaderValue($retVal)) {
2932  throw new \UnexpectedValueException(
2933  '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.',
2934  1396795884
2935  );
2936  }
2937  break;
2938  case 'HTTP_REFERER':
2939 
2940  case 'HTTP_USER_AGENT':
2941 
2942  case 'HTTP_ACCEPT_ENCODING':
2943 
2944  case 'HTTP_ACCEPT_LANGUAGE':
2945 
2946  case 'REMOTE_HOST':
2947 
2948  case 'QUERY_STRING':
2949  $retVal = '';
2950  if (isset($_SERVER[$getEnvName])) {
2951  $retVal = $_SERVER[$getEnvName];
2952  }
2953  break;
2954  case 'TYPO3_DOCUMENT_ROOT':
2955  // Get the web root (it is not the root of the TYPO3 installation)
2956  // The absolute path of the script can be calculated with TYPO3_DOCUMENT_ROOT + SCRIPT_FILENAME
2957  // 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.
2958  // Therefore the DOCUMENT_ROOT is now always calculated as the SCRIPT_FILENAME minus the end part shared with SCRIPT_NAME.
2959  $SFN = self::getIndpEnv('SCRIPT_FILENAME');
2960  $SN_A = explode('/', strrev(self::getIndpEnv('SCRIPT_NAME')));
2961  $SFN_A = explode('/', strrev($SFN));
2962  $acc = [];
2963  foreach ($SN_A as $kk => $vv) {
2964  if ((string)$SFN_A[$kk] === (string)$vv) {
2965  $acc[] = $vv;
2966  } else {
2967  break;
2968  }
2969  }
2970  $commonEnd = strrev(implode('/', $acc));
2971  if ((string)$commonEnd !== '') {
2972  $DR = substr($SFN, 0, -(strlen($commonEnd) + 1));
2973  }
2974  $retVal = $DR;
2975  break;
2976  case 'TYPO3_HOST_ONLY':
2977  $httpHost = self::getIndpEnv('HTTP_HOST');
2978  $httpHostBracketPosition = strpos($httpHost, ']');
2979  $httpHostParts = explode(':', $httpHost);
2980  $retVal = $httpHostBracketPosition !== false ? substr($httpHost, 0, $httpHostBracketPosition + 1) : array_shift($httpHostParts);
2981  break;
2982  case 'TYPO3_PORT':
2983  $httpHost = self::getIndpEnv('HTTP_HOST');
2984  $httpHostOnly = self::getIndpEnv('TYPO3_HOST_ONLY');
2985  $retVal = strlen($httpHost) > strlen($httpHostOnly) ? substr($httpHost, strlen($httpHostOnly) + 1) : '';
2986  break;
2987  case 'TYPO3_REQUEST_HOST':
2988  $retVal = (self::getIndpEnv('TYPO3_SSL') ? 'https://' : 'http://') . self::getIndpEnv('HTTP_HOST');
2989  break;
2990  case 'TYPO3_REQUEST_URL':
2991  $retVal = self::getIndpEnv('TYPO3_REQUEST_HOST') . self::getIndpEnv('REQUEST_URI');
2992  break;
2993  case 'TYPO3_REQUEST_SCRIPT':
2994  $retVal = self::getIndpEnv('TYPO3_REQUEST_HOST') . self::getIndpEnv('SCRIPT_NAME');
2995  break;
2996  case 'TYPO3_REQUEST_DIR':
2997  $retVal = self::getIndpEnv('TYPO3_REQUEST_HOST') . self::dirname(self::getIndpEnv('SCRIPT_NAME')) . '/';
2998  break;
2999  case 'TYPO3_SITE_URL':
3000  $url = self::getIndpEnv('TYPO3_REQUEST_DIR');
3001  // This can only be set by external entry scripts
3002  if (defined('TYPO3_PATH_WEB')) {
3003  $retVal = $url;
3004  } elseif (defined('PATH_thisScript') && defined('PATH_site')) {
3005  $lPath = PathUtility::stripPathSitePrefix(dirname(PATH_thisScript)) . '/';
3006  $siteUrl = substr($url, 0, -strlen($lPath));
3007  if (substr($siteUrl, -1) != '/') {
3008  $siteUrl .= '/';
3009  }
3010  $retVal = $siteUrl;
3011  }
3012  break;
3013  case 'TYPO3_SITE_PATH':
3014  $retVal = substr(self::getIndpEnv('TYPO3_SITE_URL'), strlen(self::getIndpEnv('TYPO3_REQUEST_HOST')));
3015  break;
3016  case 'TYPO3_SITE_SCRIPT':
3017  $retVal = substr(self::getIndpEnv('TYPO3_REQUEST_URL'), strlen(self::getIndpEnv('TYPO3_SITE_URL')));
3018  break;
3019  case 'TYPO3_SSL':
3020  $proxySSL = trim($GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxySSL']);
3021  if ($proxySSL == '*') {
3022  $proxySSL = $GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyIP'];
3023  }
3024  if (self::cmpIP($_SERVER['REMOTE_ADDR'], $proxySSL)) {
3025  $retVal = true;
3026  } else {
3027  $retVal = $_SERVER['SSL_SESSION_ID'] || strtolower($_SERVER['HTTPS']) === 'on' || (string)$_SERVER['HTTPS'] === '1';
3028  }
3029  break;
3030  case '_ARRAY':
3031  $out = [];
3032  // Here, list ALL possible keys to this function for debug display.
3033  $envTestVars = [
3034  'HTTP_HOST',
3035  'TYPO3_HOST_ONLY',
3036  'TYPO3_PORT',
3037  'PATH_INFO',
3038  'QUERY_STRING',
3039  'REQUEST_URI',
3040  'HTTP_REFERER',
3041  'TYPO3_REQUEST_HOST',
3042  'TYPO3_REQUEST_URL',
3043  'TYPO3_REQUEST_SCRIPT',
3044  'TYPO3_REQUEST_DIR',
3045  'TYPO3_SITE_URL',
3046  'TYPO3_SITE_SCRIPT',
3047  'TYPO3_SSL',
3048  'TYPO3_REV_PROXY',
3049  'SCRIPT_NAME',
3050  'TYPO3_DOCUMENT_ROOT',
3051  'SCRIPT_FILENAME',
3052  'REMOTE_ADDR',
3053  'REMOTE_HOST',
3054  'HTTP_USER_AGENT',
3055  'HTTP_ACCEPT_LANGUAGE'
3056  ];
3057  foreach ($envTestVars as $v) {
3058  $out[$v] = self::getIndpEnv($v);
3059  }
3060  reset($out);
3061  $retVal = $out;
3062  break;
3063  }
3064  self::$indpEnvCache[$getEnvName] = $retVal;
3065  return $retVal;
3066  }
3067 
3076  public static function isAllowedHostHeaderValue($hostHeaderValue)
3077  {
3078  if (static::$allowHostHeaderValue === true) {
3079  return true;
3080  }
3081 
3082  if (static::isInternalRequestType()) {
3083  return static::$allowHostHeaderValue = true;
3084  }
3085 
3086  // Deny the value if trusted host patterns is empty, which means we are early in the bootstrap
3087  if (empty($GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'])) {
3088  return false;
3089  }
3090 
3091  if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] === self::ENV_TRUSTED_HOSTS_PATTERN_ALLOW_ALL) {
3092  static::$allowHostHeaderValue = true;
3093  } else {
3094  static::$allowHostHeaderValue = static::hostHeaderValueMatchesTrustedHostsPattern($hostHeaderValue);
3095  }
3096 
3097  return static::$allowHostHeaderValue;
3098  }
3099 
3107  public static function hostHeaderValueMatchesTrustedHostsPattern($hostHeaderValue)
3108  {
3109  if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] === self::ENV_TRUSTED_HOSTS_PATTERN_SERVER_NAME) {
3110  // Allow values that equal the server name
3111  // Note that this is only secure if name base virtual host are configured correctly in the webserver
3112  $defaultPort = self::getIndpEnv('TYPO3_SSL') ? '443' : '80';
3113  $parsedHostValue = parse_url('http://' . $hostHeaderValue);
3114  if (isset($parsedHostValue['port'])) {
3115  $hostMatch = (strtolower($parsedHostValue['host']) === strtolower($_SERVER['SERVER_NAME']) && (string)$parsedHostValue['port'] === $_SERVER['SERVER_PORT']);
3116  } else {
3117  $hostMatch = (strtolower($hostHeaderValue) === strtolower($_SERVER['SERVER_NAME']) && $defaultPort === $_SERVER['SERVER_PORT']);
3118  }
3119  } else {
3120  // In case name based virtual hosts are not possible, we allow setting a trusted host pattern
3121  // See https://typo3.org/teams/security/security-bulletins/typo3-core/typo3-core-sa-2014-001/ for further details
3122  $hostMatch = (bool)preg_match('/^' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] . '$/i', $hostHeaderValue);
3123  }
3124 
3125  return $hostMatch;
3126  }
3127 
3138  protected static function isInternalRequestType()
3139  {
3140  return !defined('TYPO3_REQUESTTYPE') || (defined('TYPO3_REQUESTTYPE') && TYPO3_REQUESTTYPE & (TYPO3_REQUESTTYPE_INSTALL | TYPO3_REQUESTTYPE_CLI));
3141  }
3142 
3148  public static function milliseconds()
3149  {
3150  return round(microtime(true) * 1000);
3151  }
3152 
3159  public static function clientInfo($useragent = '')
3160  {
3161  if (!$useragent) {
3162  $useragent = self::getIndpEnv('HTTP_USER_AGENT');
3163  }
3164  $bInfo = [];
3165  // Which browser?
3166  if (strpos($useragent, 'Konqueror') !== false) {
3167  $bInfo['BROWSER'] = 'konqu';
3168  } elseif (strpos($useragent, 'Opera') !== false) {
3169  $bInfo['BROWSER'] = 'opera';
3170  } elseif (strpos($useragent, 'MSIE') !== false) {
3171  $bInfo['BROWSER'] = 'msie';
3172  } elseif (strpos($useragent, 'Mozilla') !== false) {
3173  $bInfo['BROWSER'] = 'net';
3174  } elseif (strpos($useragent, 'Flash') !== false) {
3175  $bInfo['BROWSER'] = 'flash';
3176  }
3177  if (isset($bInfo['BROWSER'])) {
3178  // Browser version
3179  switch ($bInfo['BROWSER']) {
3180  case 'net':
3181  $bInfo['VERSION'] = (float)substr($useragent, 8);
3182  if (strpos($useragent, 'Netscape6/') !== false) {
3183  $bInfo['VERSION'] = (float)substr(strstr($useragent, 'Netscape6/'), 10);
3184  }
3185  // Will we ever know if this was a typo or intention...?! :-(
3186  if (strpos($useragent, 'Netscape/6') !== false) {
3187  $bInfo['VERSION'] = (float)substr(strstr($useragent, 'Netscape/6'), 10);
3188  }
3189  if (strpos($useragent, 'Netscape/7') !== false) {
3190  $bInfo['VERSION'] = (float)substr(strstr($useragent, 'Netscape/7'), 9);
3191  }
3192  break;
3193  case 'msie':
3194  $tmp = strstr($useragent, 'MSIE');
3195  $bInfo['VERSION'] = (float)preg_replace('/^[^0-9]*/', '', substr($tmp, 4));
3196  break;
3197  case 'opera':
3198  $tmp = strstr($useragent, 'Opera');
3199  $bInfo['VERSION'] = (float)preg_replace('/^[^0-9]*/', '', substr($tmp, 5));
3200  break;
3201  case 'konqu':
3202  $tmp = strstr($useragent, 'Konqueror/');
3203  $bInfo['VERSION'] = (float)substr($tmp, 10);
3204  break;
3205  }
3206  // Client system
3207  if (strpos($useragent, 'Win') !== false) {
3208  $bInfo['SYSTEM'] = 'win';
3209  } elseif (strpos($useragent, 'Mac') !== false) {
3210  $bInfo['SYSTEM'] = 'mac';
3211  } elseif (strpos($useragent, 'Linux') !== false || strpos($useragent, 'X11') !== false || strpos($useragent, 'SGI') !== false || strpos($useragent, ' SunOS ') !== false || strpos($useragent, ' HP-UX ') !== false) {
3212  $bInfo['SYSTEM'] = 'unix';
3213  }
3214  }
3215  return $bInfo;
3216  }
3217 
3224  public static function getHostname($requestHost = true)
3225  {
3226  $host = '';
3227  // If not called from the command-line, resolve on getIndpEnv()
3228  if ($requestHost && !(TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_CLI)) {
3229  $host = self::getIndpEnv('HTTP_HOST');
3230  }
3231  if (!$host) {
3232  // will fail for PHP 4.1 and 4.2
3233  $host = @php_uname('n');
3234  // 'n' is ignored in broken installations
3235  if (strpos($host, ' ')) {
3236  $host = '';
3237  }
3238  }
3239  // We have not found a FQDN yet
3240  if ($host && strpos($host, '.') === false) {
3241  $ip = gethostbyname($host);
3242  // We got an IP address
3243  if ($ip != $host) {
3244  $fqdn = gethostbyaddr($ip);
3245  if ($ip != $fqdn) {
3246  $host = $fqdn;
3247  }
3248  }
3249  }
3250  if (!$host) {
3251  $host = 'localhost.localdomain';
3252  }
3253  return $host;
3254  }
3255 
3256  /*************************
3257  *
3258  * TYPO3 SPECIFIC FUNCTIONS
3259  *
3260  *************************/
3272  public static function getFileAbsFileName($filename, $_ = null, $_2 = null)
3273  {
3274  if ((string)$filename === '') {
3275  return '';
3276  }
3277  if ($_ !== null) {
3278  self::deprecationLog('Parameter 2 of GeneralUtility::getFileAbsFileName is obsolete and can be omitted.');
3279  }
3280  if ($_2 !== null) {
3281  self::deprecationLog('Parameter 3 of GeneralUtility::getFileAbsFileName is obsolete and can be omitted.');
3282  }
3283 
3284  // Extension
3285  if (strpos($filename, 'EXT:') === 0) {
3286  list($extKey, $local) = explode('/', substr($filename, 4), 2);
3287  $filename = '';
3288  if ((string)$extKey !== '' && ExtensionManagementUtility::isLoaded($extKey) && (string)$local !== '') {
3289  $filename = ExtensionManagementUtility::extPath($extKey) . $local;
3290  }
3291  } elseif (!static::isAbsPath($filename)) {
3292  // is relative. Prepended with PATH_site
3293  $filename = PATH_site . $filename;
3294  } elseif (!static::isFirstPartOfStr($filename, PATH_site)) {
3295  // absolute, but set to blank if not allowed
3296  $filename = '';
3297  }
3298  if ((string)$filename !== '' && static::validPathStr($filename)) {
3299  // checks backpath.
3300  return $filename;
3301  } else {
3302  return '';
3303  }
3304  }
3305 
3317  public static function validPathStr($theFile)
3318  {
3319  return strpos($theFile, '//') === false && strpos($theFile, '\\') === false
3320  && preg_match('#(?:^\\.\\.|/\\.\\./|[[:cntrl:]])#u', $theFile) === 0;
3321  }
3322 
3329  public static function isAbsPath($path)
3330  {
3331  return $path[0] === '/' || TYPO3_OS === 'WIN' && (strpos($path, ':/') === 1 || strpos($path, ':\\') === 1);
3332  }
3333 
3340  public static function isAllowedAbsPath($path)
3341  {
3342  $lockRootPath = $GLOBALS['TYPO3_CONF_VARS']['BE']['lockRootPath'];
3343  return static::isAbsPath($path) && static::validPathStr($path)
3344  && (static::isFirstPartOfStr($path, PATH_site)
3345  || $lockRootPath && static::isFirstPartOfStr($path, $lockRootPath));
3346  }
3347 
3357  public static function verifyFilenameAgainstDenyPattern($filename)
3358  {
3359  $pattern = '/[[:cntrl:]]/';
3360  if ((string)$filename !== '' && (string)$GLOBALS['TYPO3_CONF_VARS']['BE']['fileDenyPattern'] !== '') {
3361  $pattern = '/(?:[[:cntrl:]]|' . $GLOBALS['TYPO3_CONF_VARS']['BE']['fileDenyPattern'] . ')/i';
3362  }
3363  return !preg_match($pattern, $filename);
3364  }
3365 
3372  public static function copyDirectory($source, $destination)
3373  {
3374  if (strpos($source, PATH_site) === false) {
3375  $source = PATH_site . $source;
3376  }
3377  if (strpos($destination, PATH_site) === false) {
3378  $destination = PATH_site . $destination;
3379  }
3380  if (static::isAllowedAbsPath($source) && static::isAllowedAbsPath($destination)) {
3381  $iterator = new \RecursiveIteratorIterator(
3382  new \RecursiveDirectoryIterator($source, \RecursiveDirectoryIterator::SKIP_DOTS),
3383  \RecursiveIteratorIterator::SELF_FIRST
3384  );
3385  foreach ($iterator as $item) {
3386  $target = $destination . '/' . $iterator->getSubPathName();
3387  if ($item->isDir()) {
3388  static::mkdir($target);
3389  } else {
3390  static::upload_copy_move($item, $target);
3391  }
3392  }
3393  }
3394  }
3395 
3406  public static function sanitizeLocalUrl($url = '')
3407  {
3408  $sanitizedUrl = '';
3409  if (!empty($url)) {
3410  $decodedUrl = rawurldecode($url);
3411  $parsedUrl = parse_url($decodedUrl);
3412  $testAbsoluteUrl = self::resolveBackPath($decodedUrl);
3413  $testRelativeUrl = self::resolveBackPath(self::dirname(self::getIndpEnv('SCRIPT_NAME')) . '/' . $decodedUrl);
3414  // Pass if URL is on the current host:
3415  if (self::isValidUrl($decodedUrl)) {
3416  if (self::isOnCurrentHost($decodedUrl) && strpos($decodedUrl, self::getIndpEnv('TYPO3_SITE_URL')) === 0) {
3417  $sanitizedUrl = $url;
3418  }
3419  } elseif (self::isAbsPath($decodedUrl) && self::isAllowedAbsPath($decodedUrl)) {
3420  $sanitizedUrl = $url;
3421  } elseif (strpos($testAbsoluteUrl, self::getIndpEnv('TYPO3_SITE_PATH')) === 0 && $decodedUrl[0] === '/') {
3422  $sanitizedUrl = $url;
3423  } elseif (empty($parsedUrl['scheme']) && strpos($testRelativeUrl, self::getIndpEnv('TYPO3_SITE_PATH')) === 0
3424  && $decodedUrl[0] !== '/' && strpbrk($decodedUrl, '*:|"<>') === false && strpos($decodedUrl, '\\\\') === false
3425  ) {
3426  $sanitizedUrl = $url;
3427  }
3428  }
3429  if (!empty($url) && empty($sanitizedUrl)) {
3430  self::sysLog('The URL "' . $url . '" is not considered to be local and was denied.', 'core', self::SYSLOG_SEVERITY_NOTICE);
3431  }
3432  return $sanitizedUrl;
3433  }
3434 
3443  public static function upload_copy_move($source, $destination)
3444  {
3445  $result = false;
3446  if (is_uploaded_file($source)) {
3447  // Return the value of move_uploaded_file, and if FALSE the temporary $source is still
3448  // around so the user can use unlink to delete it:
3449  $result = move_uploaded_file($source, $destination);
3450  } else {
3451  @copy($source, $destination);
3452  }
3453  // Change the permissions of the file
3454  self::fixPermissions($destination);
3455  // If here the file is copied and the temporary $source is still around,
3456  // so when returning FALSE the user can try unlink to delete the $source
3457  return $result;
3458  }
3459 
3469  public static function upload_to_tempfile($uploadedFileName)
3470  {
3471  if (is_uploaded_file($uploadedFileName)) {
3472  $tempFile = self::tempnam('upload_temp_');
3473  move_uploaded_file($uploadedFileName, $tempFile);
3474  return @is_file($tempFile) ? $tempFile : '';
3475  }
3476  }
3477 
3487  public static function unlink_tempfile($uploadedTempFileName)
3488  {
3489  if ($uploadedTempFileName) {
3490  $uploadedTempFileName = self::fixWindowsFilePath($uploadedTempFileName);
3491  if (
3492  self::validPathStr($uploadedTempFileName)
3493  && self::isFirstPartOfStr($uploadedTempFileName, PATH_site . 'typo3temp/')
3494  && @is_file($uploadedTempFileName)
3495  ) {
3496  if (unlink($uploadedTempFileName)) {
3497  return true;
3498  }
3499  }
3500  }
3501  }
3502 
3513  public static function tempnam($filePrefix, $fileSuffix = '')
3514  {
3515  $temporaryPath = PATH_site . 'typo3temp/var/transient/';
3516  if (!is_dir($temporaryPath)) {
3517  self::mkdir_deep($temporaryPath);
3518  }
3519  if ($fileSuffix === '') {
3520  $tempFileName = static::fixWindowsFilePath(tempnam($temporaryPath, $filePrefix));
3521  } else {
3522  do {
3523  $tempFileName = $temporaryPath . $filePrefix . mt_rand(1, PHP_INT_MAX) . $fileSuffix;
3524  } while (file_exists($tempFileName));
3525  touch($tempFileName);
3526  clearstatcache(null, $tempFileName);
3527  }
3528  return $tempFileName;
3529  }
3530 
3539  public static function stdAuthCode($uid_or_record, $fields = '', $codeLength = 8)
3540  {
3541  if (is_array($uid_or_record)) {
3542  $recCopy_temp = [];
3543  if ($fields) {
3544  $fieldArr = self::trimExplode(',', $fields, true);
3545  foreach ($fieldArr as $k => $v) {
3546  $recCopy_temp[$k] = $uid_or_record[$v];
3547  }
3548  } else {
3549  $recCopy_temp = $uid_or_record;
3550  }
3551  $preKey = implode('|', $recCopy_temp);
3552  } else {
3553  $preKey = $uid_or_record;
3554  }
3555  $authCode = $preKey . '||' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'];
3556  $authCode = substr(md5($authCode), 0, $codeLength);
3557  return $authCode;
3558  }
3559 
3566  public static function hideIfNotTranslated($l18n_cfg_fieldValue)
3567  {
3568  return $GLOBALS['TYPO3_CONF_VARS']['FE']['hidePagesIfNotTranslatedByDefault'] xor ($l18n_cfg_fieldValue & 2);
3569  }
3570 
3578  public static function hideIfDefaultLanguage($localizationConfiguration)
3579  {
3580  return (bool)($localizationConfiguration & 1);
3581  }
3582 
3591  public static function llXmlAutoFileName($fileRef, $language, $sameLocation = false)
3592  {
3593  // If $fileRef is already prefixed with "[language key]" then we should return it as is
3594  $fileName = basename($fileRef);
3595  if (self::isFirstPartOfStr($fileName, $language . '.')) {
3596  return $fileRef;
3597  }
3598 
3599  if ($sameLocation) {
3600  return str_replace($fileName, $language . '.' . $fileName, $fileRef);
3601  }
3602 
3603  // Analyse file reference:
3604  // Is system:
3605  if (self::isFirstPartOfStr($fileRef, PATH_typo3 . 'sysext/')) {
3606  $validatedPrefix = PATH_typo3 . 'sysext/';
3607  } elseif (self::isFirstPartOfStr($fileRef, PATH_typo3 . 'ext/')) {
3608  // Is global:
3609  $validatedPrefix = PATH_typo3 . 'ext/';
3610  } elseif (self::isFirstPartOfStr($fileRef, PATH_typo3conf . 'ext/')) {
3611  // Is local:
3612  $validatedPrefix = PATH_typo3conf . 'ext/';
3613  } else {
3614  $validatedPrefix = '';
3615  }
3616  if ($validatedPrefix) {
3617  // Divide file reference into extension key, directory (if any) and base name:
3618  list($file_extKey, $file_extPath) = explode('/', substr($fileRef, strlen($validatedPrefix)), 2);
3619  $temp = self::revExplode('/', $file_extPath, 2);
3620  if (count($temp) === 1) {
3621  array_unshift($temp, '');
3622  }
3623  // Add empty first-entry if not there.
3624  list($file_extPath, $file_fileName) = $temp;
3625  // The filename is prefixed with "[language key]." because it prevents the llxmltranslate tool from detecting it.
3626  $location = 'typo3conf/l10n/' . $language . '/' . $file_extKey . '/' . ($file_extPath ? $file_extPath . '/' : '');
3627  return $location . $language . '.' . $file_fileName;
3628  }
3629  return null;
3630  }
3631 
3641  public static function resolveSheetDefInDS($dataStructArray, $sheet = 'sDEF')
3642  {
3643  self::logDeprecatedFunction();
3644  if (!is_array($dataStructArray)) {
3645  return 'Data structure must be an array';
3646  }
3647  if (is_array($dataStructArray['sheets'])) {
3648  $singleSheet = false;
3649  if (!isset($dataStructArray['sheets'][$sheet])) {
3650  $sheet = 'sDEF';
3651  }
3652  $dataStruct = $dataStructArray['sheets'][$sheet];
3653  // If not an array, but still set, then regard it as a relative reference to a file:
3654  if ($dataStruct && !is_array($dataStruct)) {
3655  $file = self::getFileAbsFileName($dataStruct);
3656  if ($file && @is_file($file)) {
3657  $dataStruct = self::xml2array(file_get_contents($file));
3658  }
3659  }
3660  } else {
3661  $singleSheet = true;
3662  $dataStruct = $dataStructArray;
3663  if (isset($dataStruct['meta'])) {
3664  unset($dataStruct['meta']);
3665  }
3666  // Meta data should not appear there.
3667  // Default sheet
3668  $sheet = 'sDEF';
3669  }
3670  return [$dataStruct, $sheet, $singleSheet];
3671  }
3672 
3681  public static function resolveAllSheetsInDS(array $dataStructArray)
3682  {
3683  self::logDeprecatedFunction();
3684  if (is_array($dataStructArray['sheets'])) {
3685  $out = ['sheets' => []];
3686  foreach ($dataStructArray['sheets'] as $sheetId => $sDat) {
3687  list($ds, $aS) = self::resolveSheetDefInDS($dataStructArray, $sheetId);
3688  if ($sheetId == $aS) {
3689  $out['sheets'][$aS] = $ds;
3690  }
3691  }
3692  } else {
3693  list($ds) = self::resolveSheetDefInDS($dataStructArray);
3694  $out = ['sheets' => ['sDEF' => $ds]];
3695  }
3696  return $out;
3697  }
3698 
3711  public static function callUserFunction($funcName, &$params, &$ref, $_ = '', $errorMode = 0)
3712  {
3713  $content = false;
3714  // Check if we're using a closure and invoke it directly.
3715  if (is_object($funcName) && is_a($funcName, 'Closure')) {
3716  return call_user_func_array($funcName, [&$params, &$ref]);
3717  }
3718  $funcName = trim($funcName);
3719  // Check persistent object and if found, call directly and exit.
3720  if (isset($GLOBALS['T3_VAR']['callUserFunction'][$funcName]) && is_array($GLOBALS['T3_VAR']['callUserFunction'][$funcName])) {
3721  return call_user_func_array([
3722  &$GLOBALS['T3_VAR']['callUserFunction'][$funcName]['obj'],
3723  $GLOBALS['T3_VAR']['callUserFunction'][$funcName]['method']
3724  ], [&$params, &$ref]);
3725  }
3726  // Check file-reference prefix; if found, require_once() the file (should be library of code)
3727  if (strpos($funcName, ':') !== false) {
3728  // @deprecated since TYPO3 v8, will be removed in v9
3729  self::deprecationLog('Using file references to resolve "' . $funcName . '" has been deprecated in TYPO3 v8 '
3730  . 'when calling GeneralUtility::callUserFunction(), make sure the class is available via the class loader. '
3731  . 'This functionality will be removed in TYPO3 v9.');
3732  list($file, $funcRef) = self::revExplode(':', $funcName, 2);
3733  $requireFile = self::getFileAbsFileName($file);
3734  if ($requireFile) {
3735  require_once $requireFile;
3736  }
3737  } else {
3738  $funcRef = $funcName;
3739  }
3740  // Check for persistent object token, "&"
3741  if ($funcRef[0] === '&') {
3742  self::deprecationLog('Using the persistent object token "&" when resolving "' . $funcRef . '" for '
3743  . 'GeneralUtility::callUserFunc() is deprecated since TYPO3 v8. Make sure to implement '
3744  . 'SingletonInterface to achieve the same functionality. This functionality will be removed in TYPO3 v9 '
3745  . 'and will then result in a fatal PHP error.');
3746  $funcRef = substr($funcRef, 1);
3747  $storePersistentObject = true;
3748  } else {
3749  $storePersistentObject = false;
3750  }
3751  // Call function or method:
3752  $parts = explode('->', $funcRef);
3753  if (count($parts) === 2) {
3754  // Class
3755  // Check if class/method exists:
3756  if (class_exists($parts[0])) {
3757  // Get/Create object of class:
3758  if ($storePersistentObject) {
3759  // Get reference to current instance of class:
3760  if (!is_object($GLOBALS['T3_VAR']['callUserFunction_classPool'][$parts[0]])) {
3761  $GLOBALS['T3_VAR']['callUserFunction_classPool'][$parts[0]] = self::makeInstance($parts[0]);
3762  }
3763  $classObj = $GLOBALS['T3_VAR']['callUserFunction_classPool'][$parts[0]];
3764  } else {
3765  // Create new object:
3766  $classObj = self::makeInstance($parts[0]);
3767  }
3768  if (method_exists($classObj, $parts[1])) {
3769  // If persistent object should be created, set reference:
3770  if ($storePersistentObject) {
3771  $GLOBALS['T3_VAR']['callUserFunction'][$funcName] = [
3772  'method' => $parts[1],
3773  'obj' => &$classObj
3774  ];
3775  }
3776  // Call method:
3777  $content = call_user_func_array([&$classObj, $parts[1]], [&$params, &$ref]);
3778  } else {
3779  $errorMsg = 'No method name \'' . $parts[1] . '\' in class ' . $parts[0];
3780  if ($errorMode == 2) {
3781  throw new \InvalidArgumentException($errorMsg, 1294585865);
3782  } elseif (!$errorMode) {
3783  debug($errorMsg, \TYPO3\CMS\Core\Utility\GeneralUtility::class . '::callUserFunction');
3784  }
3785  }
3786  } else {
3787  $errorMsg = 'No class named ' . $parts[0];
3788  if ($errorMode == 2) {
3789  throw new \InvalidArgumentException($errorMsg, 1294585866);
3790  } elseif (!$errorMode) {
3791  debug($errorMsg, \TYPO3\CMS\Core\Utility\GeneralUtility::class . '::callUserFunction');
3792  }
3793  }
3794  } else {
3795  // Function
3796  if (function_exists($funcRef)) {
3797  $content = call_user_func_array($funcRef, [&$params, &$ref]);
3798  } else {
3799  $errorMsg = 'No function named: ' . $funcRef;
3800  if ($errorMode == 2) {
3801  throw new \InvalidArgumentException($errorMsg, 1294585867);
3802  } elseif (!$errorMode) {
3803  debug($errorMsg, \TYPO3\CMS\Core\Utility\GeneralUtility::class . '::callUserFunction');
3804  }
3805  }
3806  }
3807  return $content;
3808  }
3809 
3827  public static function getUserObj($classRef)
3828  {
3829  // Check file-reference prefix; if found, require_once() the file (should be library of code)
3830  if (strpos($classRef, ':') !== false) {
3831  // @deprecated since TYPO3 v8, will be removed in v9
3832  self::deprecationLog('Using file references to resolve "' . $classRef . '" has been deprecated in TYPO3 v8 '
3833  . 'when calling GeneralUtility::getUserObj(), make sure the class is available via the class loader. '
3834  . 'This functionality will be removed in TYPO3 v9.');
3835  list($file, $classRef) = self::revExplode(':', $classRef, 2);
3836  $requireFile = self::getFileAbsFileName($file);
3837  if ($requireFile) {
3838  require_once $requireFile;
3839  }
3840  }
3841 
3842  // Check if class exists:
3843  if (class_exists($classRef)) {
3844  return self::makeInstance($classRef);
3845  }
3846  }
3847 
3867  public static function makeInstance($className, ...$constructorArguments)
3868  {
3869  if (!is_string($className) || empty($className)) {
3870  throw new \InvalidArgumentException('$className must be a non empty string.', 1288965219);
3871  }
3872  // Never instantiate with a beginning backslash, otherwise things like singletons won't work.
3873  if ($className[0] === '\\') {
3874  throw new \InvalidArgumentException(
3875  '$className "' . $className . '" must not start with a backslash.', 1420281366
3876  );
3877  }
3878  if (isset(static::$finalClassNameCache[$className])) {
3879  $finalClassName = static::$finalClassNameCache[$className];
3880  } else {
3881  $finalClassName = self::getClassName($className);
3882  static::$finalClassNameCache[$className] = $finalClassName;
3883  }
3884  // Return singleton instance if it is already registered
3885  if (isset(self::$singletonInstances[$finalClassName])) {
3886  return self::$singletonInstances[$finalClassName];
3887  }
3888  // Return instance if it has been injected by addInstance()
3889  if (
3890  isset(self::$nonSingletonInstances[$finalClassName])
3891  && !empty(self::$nonSingletonInstances[$finalClassName])
3892  ) {
3893  return array_shift(self::$nonSingletonInstances[$finalClassName]);
3894  }
3895  // Create new instance and call constructor with parameters
3896  $instance = new $finalClassName(...$constructorArguments);
3897  // Register new singleton instance
3898  if ($instance instanceof SingletonInterface) {
3899  self::$singletonInstances[$finalClassName] = $instance;
3900  }
3901  return $instance;
3902  }
3903 
3911  protected static function getClassName($className)
3912  {
3913  if (class_exists($className)) {
3914  while (static::classHasImplementation($className)) {
3915  $className = static::getImplementationForClass($className);
3916  }
3917  }
3919  }
3920 
3927  protected static function getImplementationForClass($className)
3928  {
3929  return $GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][$className]['className'];
3930  }
3931 
3938  protected static function classHasImplementation($className)
3939  {
3940  // If we are early in the bootstrap, the configuration might not yet be present
3941  if (!isset($GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'])) {
3942  return false;
3943  }
3944 
3945  return isset($GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][$className])
3946  && is_array($GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][$className])
3947  && !empty($GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][$className]['className']);
3948  }
3949 
3968  public static function setSingletonInstance($className, SingletonInterface $instance)
3969  {
3970  self::checkInstanceClassName($className, $instance);
3971  self::$singletonInstances[$className] = $instance;
3972  }
3973 
3990  public static function removeSingletonInstance($className, SingletonInterface $instance)
3991  {
3992  self::checkInstanceClassName($className, $instance);
3993  if (!isset(self::$singletonInstances[$className])) {
3994  throw new \InvalidArgumentException('No Instance registered for ' . $className . '.', 1394099179);
3995  }
3996  if ($instance !== self::$singletonInstances[$className]) {
3997  throw new \InvalidArgumentException('The instance you are trying to remove has not been registered before.', 1394099256);
3998  }
3999  unset(self::$singletonInstances[$className]);
4000  }
4001 
4016  public static function resetSingletonInstances(array $newSingletonInstances)
4017  {
4018  static::$singletonInstances = [];
4019  foreach ($newSingletonInstances as $className => $instance) {
4020  static::setSingletonInstance($className, $instance);
4021  }
4022  }
4023 
4036  public static function getSingletonInstances()
4037  {
4038  return static::$singletonInstances;
4039  }
4040 
4056  public static function addInstance($className, $instance)
4057  {
4058  self::checkInstanceClassName($className, $instance);
4059  if ($instance instanceof SingletonInterface) {
4060  throw new \InvalidArgumentException('$instance must not be an instance of TYPO3\\CMS\\Core\\SingletonInterface. ' . 'For setting singletons, please use setSingletonInstance.', 1288969325);
4061  }
4062  if (!isset(self::$nonSingletonInstances[$className])) {
4063  self::$nonSingletonInstances[$className] = [];
4064  }
4065  self::$nonSingletonInstances[$className][] = $instance;
4066  }
4067 
4077  protected static function checkInstanceClassName($className, $instance)
4078  {
4079  if ($className === '') {
4080  throw new \InvalidArgumentException('$className must not be empty.', 1288967479);
4081  }
4082  if (!$instance instanceof $className) {
4083  throw new \InvalidArgumentException('$instance must be an instance of ' . $className . ', but actually is an instance of ' . get_class($instance) . '.', 1288967686);
4084  }
4085  }
4086 
4098  public static function purgeInstances()
4099  {
4100  self::$singletonInstances = [];
4101  self::$nonSingletonInstances = [];
4102  }
4103 
4112  public static function flushInternalRuntimeCaches()
4113  {
4114  self::$indpEnvCache = [];
4115  self::$idnaStringCache = [];
4116  }
4117 
4127  public static function makeInstanceService($serviceType, $serviceSubType = '', $excludeServiceKeys = [])
4128  {
4129  $error = false;
4130  if (!is_array($excludeServiceKeys)) {
4131  $excludeServiceKeys = self::trimExplode(',', $excludeServiceKeys, true);
4132  }
4133  $requestInfo = [
4134  'requestedServiceType' => $serviceType,
4135  'requestedServiceSubType' => $serviceSubType,
4136  'requestedExcludeServiceKeys' => $excludeServiceKeys
4137  ];
4138  while ($info = ExtensionManagementUtility::findService($serviceType, $serviceSubType, $excludeServiceKeys)) {
4139  // provide information about requested service to service object
4140  $info = array_merge($info, $requestInfo);
4141  // Check persistent object and if found, call directly and exit.
4142  if (is_object($GLOBALS['T3_VAR']['makeInstanceService'][$info['className']])) {
4143  // update request info in persistent object
4144  $GLOBALS['T3_VAR']['makeInstanceService'][$info['className']]->info = $info;
4145  // reset service and return object
4146  $GLOBALS['T3_VAR']['makeInstanceService'][$info['className']]->reset();
4147  return $GLOBALS['T3_VAR']['makeInstanceService'][$info['className']];
4148  } else {
4149  $obj = self::makeInstance($info['className']);
4150  if (is_object($obj)) {
4151  if (!@is_callable([$obj, 'init'])) {
4152  // use silent logging??? I don't think so.
4153  die('Broken service:' . DebugUtility::viewArray($info));
4154  }
4155  $obj->info = $info;
4156  // service available?
4157  if ($obj->init()) {
4158  // create persistent object
4159  $GLOBALS['T3_VAR']['makeInstanceService'][$info['className']] = $obj;
4160  return $obj;
4161  }
4162  $error = $obj->getLastErrorArray();
4163  unset($obj);
4164  }
4165  }
4166  // deactivate the service
4167  ExtensionManagementUtility::deactivateService($info['serviceType'], $info['serviceKey']);
4168  }
4169  return $error;
4170  }
4171 
4180  public static function requireOnce($requireFile)
4181  {
4182  self::logDeprecatedFunction();
4183  // Needed for require_once
4184  global $T3_SERVICES, $T3_VAR, $TYPO3_CONF_VARS;
4185  require_once $requireFile;
4186  }
4187 
4197  public static function requireFile($requireFile)
4198  {
4199  self::logDeprecatedFunction();
4200  // Needed for require
4201  global $T3_SERVICES, $T3_VAR, $TYPO3_CONF_VARS;
4202  require $requireFile;
4203  }
4204 
4213  public static function makeRedirectUrl($inUrl, $l = 0, $index_script_url = '')
4214  {
4215  if (strlen($inUrl) > $l) {
4216  $md5 = substr(md5($inUrl), 0, 20);
4217  $connection = self::makeInstance(ConnectionPool::class)->getConnectionForTable('cache_md5params');
4218  $count = $connection->count(
4219  '*',
4220  'cache_md5params',
4221  ['md5hash' => $md5]
4222  );
4223  if (!$count) {
4224  $connection->insert(
4225  'cache_md5params',
4226  [
4227  'md5hash' => $md5,
4228  'tstamp' => $GLOBALS['EXEC_TIME'],
4229  'type' => 2,
4230  'params' => $inUrl
4231  ]
4232  );
4233  }
4234  $inUrl = ($index_script_url ?: self::getIndpEnv('TYPO3_REQUEST_DIR') . 'index.php') . '?RDCT=' . $md5;
4235  }
4236  return $inUrl;
4237  }
4238 
4246  public static function freetypeDpiComp($fontSize)
4247  {
4248  // FreeType 2 always has 96 dpi.
4249  $dpi = 96.0;
4250  return $fontSize / $dpi * 72;
4251  }
4252 
4259  public static function initSysLog()
4260  {
4261  // For CLI logging name is <fqdn-hostname>:<TYPO3-path>
4262  if (TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_CLI) {
4263  $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_div.php']['systemLogHost'] = self::getHostname() . ':' . PATH_site;
4264  } else {
4265  $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_div.php']['systemLogHost'] = self::getIndpEnv('TYPO3_SITE_URL');
4266  }
4267  // Init custom logging
4268  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_div.php']['systemLog'])) {
4269  $params = ['initLog' => true];
4270  $fakeThis = false;
4271  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_div.php']['systemLog'] as $hookMethod) {
4272  self::callUserFunction($hookMethod, $params, $fakeThis);
4273  }
4274  }
4275  // Init TYPO3 logging
4276  foreach (explode(';', $GLOBALS['TYPO3_CONF_VARS']['SYS']['systemLog'], 2) as $log) {
4277  list($type, $destination) = explode(',', $log, 3);
4278  if ($type == 'syslog') {
4279  if (TYPO3_OS == 'WIN') {
4280  $facility = LOG_USER;
4281  } else {
4282  $facility = constant('LOG_' . strtoupper($destination));
4283  }
4284  openlog($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_div.php']['systemLogHost'], LOG_ODELAY, $facility);
4285  }
4286  }
4287  $GLOBALS['TYPO3_CONF_VARS']['SYS']['systemLogLevel'] = MathUtility::forceIntegerInRange($GLOBALS['TYPO3_CONF_VARS']['SYS']['systemLogLevel'], 0, 4);
4288  $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_div.php']['systemLogInit'] = true;
4289  }
4290 
4302  public static function sysLog($msg, $extKey, $severity = 0)
4303  {
4304  $severity = MathUtility::forceIntegerInRange($severity, 0, 4);
4305  // Is message worth logging?
4306  if ((int)$GLOBALS['TYPO3_CONF_VARS']['SYS']['systemLogLevel'] > $severity) {
4307  return;
4308  }
4309  // Initialize logging
4310  if (!$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_div.php']['systemLogInit']) {
4311  self::initSysLog();
4312  }
4313  // Do custom logging
4314  if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_div.php']['systemLog']) && is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_div.php']['systemLog'])) {
4315  $params = ['msg' => $msg, 'extKey' => $extKey, 'backTrace' => debug_backtrace(), 'severity' => $severity];
4316  $fakeThis = false;
4317  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_div.php']['systemLog'] as $hookMethod) {
4318  self::callUserFunction($hookMethod, $params, $fakeThis);
4319  }
4320  }
4321  // TYPO3 logging enabled?
4322  if (!$GLOBALS['TYPO3_CONF_VARS']['SYS']['systemLog']) {
4323  return;
4324  }
4325  $dateFormat = $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'];
4326  $timeFormat = $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'];
4327  // Use all configured logging options
4328  foreach (explode(';', $GLOBALS['TYPO3_CONF_VARS']['SYS']['systemLog'], 2) as $log) {
4329  list($type, $destination, $level) = explode(',', $log, 4);
4330  // Is message worth logging for this log type?
4331  if ((int)$level > $severity) {
4332  continue;
4333  }
4334  $msgLine = ' - ' . $extKey . ': ' . $msg;
4335  // Write message to a file
4336  if ($type == 'file') {
4337  $file = fopen($destination, 'a');
4338  if ($file) {
4339  fwrite($file, date(($dateFormat . ' ' . $timeFormat)) . $msgLine . LF);
4340  fclose($file);
4341  self::fixPermissions($destination);
4342  }
4343  } elseif ($type == 'mail') {
4344  list($to, $from) = explode('/', $destination);
4345  if (!self::validEmail($from)) {
4346  $from = MailUtility::getSystemFrom();
4347  }
4349  $mail = self::makeInstance(\TYPO3\CMS\Core\Mail\MailMessage::class);
4350  $mail->setTo($to)->setFrom($from)->setSubject('Warning - error in TYPO3 installation')->setBody('Host: ' . $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_div.php']['systemLogHost'] . LF . 'Extension: ' . $extKey . LF . 'Severity: ' . $severity . LF . LF . $msg);
4351  $mail->send();
4352  } elseif ($type == 'error_log') {
4353  error_log($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_div.php']['systemLogHost'] . $msgLine, 0);
4354  } elseif ($type == 'syslog') {
4355  $priority = [LOG_INFO, LOG_NOTICE, LOG_WARNING, LOG_ERR, LOG_CRIT];
4356  syslog($priority[(int)$severity], $msgLine);
4357  }
4358  }
4359  }
4360 
4375  public static function devLog($msg, $extKey, $severity = 0, $dataVar = false)
4376  {
4377  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_div.php']['devLog'])) {
4378  $params = ['msg' => $msg, 'extKey' => $extKey, 'severity' => $severity, 'dataVar' => $dataVar];
4379  $fakeThis = false;
4380  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_div.php']['devLog'] as $hookMethod) {
4381  self::callUserFunction($hookMethod, $params, $fakeThis);
4382  }
4383  }
4384  }
4385 
4392  public static function deprecationLog($msg)
4393  {
4394  if (!$GLOBALS['TYPO3_CONF_VARS']['SYS']['enableDeprecationLog']) {
4395  return;
4396  }
4397  // Legacy values (no strict comparison, $log can be boolean, string or int)
4398  $log = $GLOBALS['TYPO3_CONF_VARS']['SYS']['enableDeprecationLog'];
4399  if ($log === true || $log == '1') {
4400  $log = ['file'];
4401  } else {
4402  $log = self::trimExplode(',', $GLOBALS['TYPO3_CONF_VARS']['SYS']['enableDeprecationLog'], true);
4403  }
4404  $date = date($GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] . ' ' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'] . ': ');
4405  if (in_array('file', $log) !== false) {
4406  // Write a longer message to the deprecation log
4407  $destination = static::getDeprecationLogFileName();
4408  $file = @fopen($destination, 'a');
4409  if ($file) {
4410  @fwrite($file, ($date . $msg . LF));
4411  @fclose($file);
4412  self::fixPermissions($destination);
4413  }
4414  }
4415  if (in_array('devlog', $log) !== false) {
4416  // Copy message also to the developer log
4417  self::devLog($msg, 'Core', self::SYSLOG_SEVERITY_WARNING);
4418  }
4419  // Do not use console in login screen
4420  if (in_array('console', $log) !== false && isset($GLOBALS['BE_USER']->user['uid'])) {
4421  DebugUtility::debug($msg, $date, 'Deprecation Log');
4422  }
4423  }
4424 
4430  public static function getDeprecationLogFileName()
4431  {
4432  return PATH_typo3conf . 'deprecation_' . self::shortMD5((PATH_site . $GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'])) . '.log';
4433  }
4434 
4441  public static function logDeprecatedFunction()
4442  {
4443  if (!$GLOBALS['TYPO3_CONF_VARS']['SYS']['enableDeprecationLog']) {
4444  return;
4445  }
4446  $trail = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
4447  if ($trail[1]['type']) {
4448  $function = new \ReflectionMethod($trail[1]['class'], $trail[1]['function']);
4449  } else {
4450  $function = new \ReflectionFunction($trail[1]['function']);
4451  }
4452  $msg = '';
4453  if (preg_match('/@deprecated\\s+(.*)/', $function->getDocComment(), $match)) {
4454  $msg = $match[1];
4455  }
4456  // Write a longer message to the deprecation log: <function> <annotion> - <trace> (<source>)
4457  $logMsg = $trail[1]['class'] . $trail[1]['type'] . $trail[1]['function'];
4458  $logMsg .= '() - ' . $msg . ' - ' . DebugUtility::debugTrail();
4459  $logMsg .= ' (' . PathUtility::stripPathSitePrefix($function->getFileName()) . '#' . $function->getStartLine() . ')';
4460  self::deprecationLog($logMsg);
4461  }
4462 
4472  public static function arrayToLogString(array $arr, $valueList = [], $valueLength = 20)
4473  {
4474  $str = '';
4475  if (!is_array($valueList)) {
4476  $valueList = self::trimExplode(',', $valueList, true);
4477  }
4478  $valListCnt = count($valueList);
4479  foreach ($arr as $key => $value) {
4480  if (!$valListCnt || in_array($key, $valueList)) {
4481  $str .= (string)$key . trim(': ' . self::fixed_lgd_cs(str_replace(LF, '|', (string)$value), $valueLength)) . '; ';
4482  }
4483  }
4484  return $str;
4485  }
4486 
4496  public static function imageMagickCommand($command, $parameters, $path = '')
4497  {
4498  self::logDeprecatedFunction();
4499  return CommandUtility::imageMagickCommand($command, $parameters, $path);
4500  }
4501 
4511  public static function unQuoteFilenames($parameters, $unQuote = false)
4512  {
4513  $paramsArr = explode(' ', trim($parameters));
4514  // Whenever a quote character (") is found, $quoteActive is set to the element number inside of $params. A value of -1 means that there are not open quotes at the current position.
4515  $quoteActive = -1;
4516  foreach ($paramsArr as $k => $v) {
4517  if ($quoteActive > -1) {
4518  $paramsArr[$quoteActive] .= ' ' . $v;
4519  unset($paramsArr[$k]);
4520  if (substr($v, -1) === $paramsArr[$quoteActive][0]) {
4521  $quoteActive = -1;
4522  }
4523  } elseif (!trim($v)) {
4524  // Remove empty elements
4525  unset($paramsArr[$k]);
4526  } elseif (preg_match('/^(["\'])/', $v) && substr($v, -1) !== $v[0]) {
4527  $quoteActive = $k;
4528  }
4529  }
4530  if ($unQuote) {
4531  foreach ($paramsArr as $key => &$val) {
4532  $val = preg_replace('/(?:^"|"$)/', '', $val);
4533  $val = preg_replace('/(?:^\'|\'$)/', '', $val);
4534  }
4535  unset($val);
4536  }
4537  // Return reindexed array
4538  return array_values($paramsArr);
4539  }
4540 
4547  public static function quoteJSvalue($value)
4548  {
4549  return strtr(
4550  json_encode((string)$value, JSON_HEX_AMP|JSON_HEX_APOS|JSON_HEX_QUOT|JSON_HEX_TAG),
4551  [
4552  '"' => '\'',
4553  '\\\\' => '\\u005C',
4554  ' ' => '\\u0020',
4555  '!' => '\\u0021',
4556  '\\t' => '\\u0009',
4557  '\\n' => '\\u000A',
4558  '\\r' => '\\u000D'
4559  ]
4560  );
4561  }
4562 
4569  public static function flushOutputBuffers()
4570  {
4571  self::logDeprecatedFunction();
4572  $obContent = '';
4573  while ($content = ob_get_clean()) {
4574  $obContent .= $content;
4575  }
4576  // If previously a "Content-Encoding: whatever" has been set, we have to unset it
4577  if (!headers_sent()) {
4578  $headersList = headers_list();
4579  foreach ($headersList as $header) {
4580  // Split it up at the :
4581  list($key, $value) = self::trimExplode(':', $header, true);
4582  // Check if we have a Content-Encoding other than 'None'
4583  if (strtolower($key) === 'content-encoding' && strtolower($value) !== 'none') {
4584  header('Content-Encoding: None');
4585  break;
4586  }
4587  }
4588  }
4589  echo $obContent;
4590  }
4591 
4603  {
4604  if (is_null(static::$applicationContext)) {
4605  static::$applicationContext = $applicationContext;
4606  } else {
4607  throw new \RuntimeException('Trying to override applicationContext which has already been defined!', 1376084316);
4608  }
4609  }
4610 
4616  public static function getApplicationContext()
4617  {
4618  return static::$applicationContext;
4619  }
4620 
4625  public static function isRunningOnCgiServerApi()
4626  {
4627  return in_array(PHP_SAPI, self::$supportedCgiServerApis, true);
4628  }
4629 }
static minifyJavaScript($script, &$error= '')
static slashJS($string, $extended=false, $char= '\'')
static linkThisUrl($url, array $getParams=[])
static upload_to_tempfile($uploadedFileName)
static xmlRecompileFromStructValArray(array $vals)
static rmdir($path, $removeNonEmpty=false)
static copyDirectory($source, $destination)
static getBytesFromSizeMeasurement($measurement)
static hideIfNotTranslated($l18n_cfg_fieldValue)
static isFirstPartOfStr($str, $partStr)
static stdAuthCode($uid_or_record, $fields= '', $codeLength=8)
static xml2array($string, $NSprefix= '', $reportDocTag=false)
static isAllowedHostHeaderValue($hostHeaderValue)
static upload_copy_move($source, $destination)
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
static presetApplicationContext(ApplicationContext $applicationContext)
static mkdir_deep($directory, $deepDirectory= '')
static explodeUrl2Array($string, $multidim=false)
static split_fileref($fileNameWithPath)
static generateRandomBytes($bytesToReturn)
static hideIfDefaultLanguage($localizationConfiguration)
static llXmlAutoFileName($fileRef, $language, $sameLocation=false)
static implodeArrayForUrl($name, array $theArray, $str= '', $skipBlank=false, $rawurlencodeParamName=false)
static makeRedirectUrl($inUrl, $l=0, $index_script_url= '')
static arrayToLogString(array $arr, $valueList=[], $valueLength=20)
static formatSize($sizeInBytes, $labels= '', $base=0)
static array2xml(array $array, $NSprefix= '', $level=0, $docTag= 'phparray', $spaceInd=0, array $options=[], array $stackData=[])
static writeFile($file, $content, $changePermissions=false)
static removePrefixPathFromList(array $fileArr, $prefixToRemove)
static imageMagickCommand($command, $parameters, $path= '')
static resolveSheetDefInDS($dataStructArray, $sheet= 'sDEF')
static verifyFilenameAgainstDenyPattern($filename)
static fixPermissions($path, $recursive=false)
static imageMagickCommand($command, $parameters, $path= '')
static xml2arrayProcess($string, $NSprefix= '', $reportDocTag=false)
static array2xml_cs(array $array, $docTag= 'phparray', array $options=[], $charset= '')
static unlink_tempfile($uploadedTempFileName)
static unQuoteFilenames($parameters, $unQuote=false)
static setSingletonInstance($className, SingletonInterface $instance)
static buildUrl(array $urlParts)
static uniqueList($in_list, $secondParameter=null)
static splitCalc($string, $operators)
static compileSelectedGetVarsFromArray($varList, array $getArray, $GPvarAlt=true)
static implodeAttributes(array $arr, $xhtmlSafe=false, $dontOmitBlankAttribs=false)
static writeFileToTypo3tempDir($filepath, $content)
static checkInstanceClassName($className, $instance)
static linkThisScript(array $getParams=[])
static findService($serviceType, $serviceSubType= '', $excludeServiceKeys=[])
static csvValues(array $row, $delim= ',', $quote= '"')
static _GETset($inputGet, $key= '')
static tempnam($filePrefix, $fileSuffix= '')
static mergeRecursiveWithOverrule(array &$original, array $overrule, $addKeys=true, $includeEmptyValues=true, $enableUnsetFeature=true)
static createDirectoryPath($fullDirectoryPath)
static resetSingletonInstances(array $newSingletonInstances)
static xml2tree($string, $depth=999, $parserOptions=[])
if(TYPO3_MODE=== 'BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
static debugTrail($prependFileNames=false)
static debug($var= '', $header= '', $group= 'Debug')
static makeInstance($className,...$constructorArguments)
static forceIntegerInRange($theInt, $min, $max=2000000000, $defaultValue=0)
Definition: MathUtility.php:31
static getFilesInDir($path, $extensionList= '', $prependPath=false, $order= '', $excludePattern= '')
debug($variable= '', $name= '*variable *', $line= '*line *', $file= '*file *', $recursiveDepth=3, $debugLevel=E_DEBUG)
static getFileAbsFileName($filename, $_=null, $_2=null)
static flushDirectory($directory, $keepOriginalDirectory=false, $flushOpcodeCache=false)
static hmac($input, $additionalSecret= '')
static removeSingletonInstance($className, SingletonInterface $instance)
static intExplode($delimiter, $string, $removeEmptyValues=false, $limit=0)
static callUserFunction($funcName, &$params, &$ref, $_= '', $errorMode=0)
static makeInstanceService($serviceType, $serviceSubType= '', $excludeServiceKeys=[])
static revExplode($delimiter, $string, $count=0)
static addInstance($className, $instance)
static devLog($msg, $extKey, $severity=0, $dataVar=false)
static getHostname($requestHost=true)
static getAllFilesAndFoldersInPath(array $fileArr, $path, $extList= '', $regDirs=false, $recursivityLevels=99, $excludePattern= '')
static hostHeaderValueMatchesTrustedHostsPattern($hostHeaderValue)
static resolveAllSheetsInDS(array $dataStructArray)