* @package TYPO3 * @subpackage t3lib */ final class t3lib_div { // Severity constants used by t3lib_div::sysLog() const SYSLOG_SEVERITY_INFO = 0; const SYSLOG_SEVERITY_NOTICE = 1; const SYSLOG_SEVERITY_WARNING = 2; const SYSLOG_SEVERITY_ERROR = 3; const SYSLOG_SEVERITY_FATAL = 4; const ENV_TRUSTED_HOSTS_PATTERN_ALLOW_ALL = '.*'; const ENV_TRUSTED_HOSTS_PATTERN_SERVER_NAME = 'SERVER_NAME'; /** * State of host header value security check * in order to avoid unnecessary multiple checks during one request * * @var bool */ static protected $allowHostHeaderValue = FALSE; /** * Singleton instances returned by makeInstance, using the class names as * array keys * * @var array */ protected static $singletonInstances = array(); /** * Instances returned by makeInstance, using the class names as array keys * * @var array */ protected static $nonSingletonInstances = array(); /** * Register for makeInstance with given class name and final class names to reduce number of class_exists() calls * * @var array Given class name => final class name */ protected static $finalClassNameRegister = array(); /************************* * * GET/POST Variables * * Background: * Input GET/POST variables in PHP may have their quotes escaped with "\" or not depending on configuration. * TYPO3 has always converted quotes to BE escaped if the configuration told that they would not be so. * But the clean solution is that quotes are never escaped and that is what the functions below offers. * Eventually TYPO3 should provide this in the global space as well. * In the transitional phase (or forever..?) we need to encourage EVERY to read and write GET/POST vars through the API functions below. * *************************/ /** * Returns the 'GLOBAL' value of incoming data from POST or GET, with priority to POST (that is equalent to 'GP' order) * Strips slashes from all output, both strings and arrays. * To enhancement security in your scripts, please consider using t3lib_div::_GET or t3lib_div::_POST if you already * know by which method your data is arriving to the scripts! * * @param string $var GET/POST var to return * @return mixed POST var named $var and if not set, the GET var of the same name. */ public static function _GP($var) { if (empty($var)) { return; } $value = isset($_POST[$var]) ? $_POST[$var] : $_GET[$var]; if (isset($value)) { if (is_array($value)) { self::stripSlashesOnArray($value); } else { $value = stripslashes($value); } } return $value; } /** * Returns the global arrays $_GET and $_POST merged with $_POST taking precedence. * * @param string $parameter Key (variable name) from GET or POST vars * @return array Returns the GET vars merged recursively onto the POST vars. */ public static function _GPmerged($parameter) { $postParameter = (isset($_POST[$parameter]) && is_array($_POST[$parameter])) ? $_POST[$parameter] : array(); $getParameter = (isset($_GET[$parameter]) && is_array($_GET[$parameter])) ? $_GET[$parameter] : array(); $mergedParameters = self::array_merge_recursive_overrule($getParameter, $postParameter); self::stripSlashesOnArray($mergedParameters); return $mergedParameters; } /** * Returns the global $_GET array (or value from) normalized to contain un-escaped values. * ALWAYS use this API function to acquire the GET variables! * * @param string $var Optional pointer to value in GET array (basically name of GET var) * @return mixed If $var is set it returns the value of $_GET[$var]. If $var is NULL (default), returns $_GET itself. In any case *slashes are stipped from the output!* * @see _POST(), _GP(), _GETset() */ public static function _GET($var = NULL) { $value = ($var === NULL) ? $_GET : (empty($var) ? NULL : $_GET[$var]); if (isset($value)) { // Removes slashes since TYPO3 has added them regardless of magic_quotes setting. if (is_array($value)) { self::stripSlashesOnArray($value); } else { $value = stripslashes($value); } } return $value; } /** * Returns the global $_POST array (or value from) normalized to contain un-escaped values. * ALWAYS use this API function to acquire the $_POST variables! * * @param string $var Optional pointer to value in POST array (basically name of POST var) * @return mixed If $var is set it returns the value of $_POST[$var]. If $var is NULL (default), returns $_POST itself. In any case *slashes are stipped from the output!* * @see _GET(), _GP() */ public static function _POST($var = NULL) { $value = ($var === NULL) ? $_POST : (empty($var) ? NULL : $_POST[$var]); if (isset($value)) { // Removes slashes since TYPO3 has added them regardless of magic_quotes setting. if (is_array($value)) { self::stripSlashesOnArray($value); } else { $value = stripslashes($value); } } return $value; } /** * Writes input value to $_GET. * * @param mixed $inputGet * array or single value to write to $_GET. Values should NOT be * escaped at input time (but will be escaped before writing * according to TYPO3 standards). * @param string $key * alternative key; If set, this will not set the WHOLE GET array, * but only the key in it specified by this value! * You can specify to replace keys on deeper array levels by * separating the keys with a pipe. * Example: 'parentKey|childKey' will result in * array('parentKey' => array('childKey' => $inputGet)) * * @return void */ public static function _GETset($inputGet, $key = '') { // adds slashes since TYPO3 standard currently is that slashes // must be applied (regardless of magic_quotes setting) if (is_array($inputGet)) { self::addSlashesOnArray($inputGet); } else { $inputGet = addslashes($inputGet); } if ($key != '') { if (strpos($key, '|') !== FALSE) { $pieces = explode('|', $key); $newGet = array(); $pointer =& $newGet; foreach ($pieces as $piece) { $pointer =& $pointer[$piece]; } $pointer = $inputGet; $mergedGet = self::array_merge_recursive_overrule( $_GET, $newGet ); $_GET = $mergedGet; $GLOBALS['HTTP_GET_VARS'] = $mergedGet; } else { $_GET[$key] = $inputGet; $GLOBALS['HTTP_GET_VARS'][$key] = $inputGet; } } elseif (is_array($inputGet)) { $_GET = $inputGet; $GLOBALS['HTTP_GET_VARS'] = $inputGet; } } /** * Wrapper for the RemoveXSS function. * Removes potential XSS code from an input string. * * Using an external class by Travis Puderbaugh * * @param string $string Input string * @return string Input string with potential XSS code removed */ public static function removeXSS($string) { require_once(PATH_typo3 . 'contrib/RemoveXSS/RemoveXSS.php'); $string = RemoveXSS::process($string); return $string; } /************************* * * IMAGE FUNCTIONS * *************************/ /** * Compressing a GIF file if not already LZW compressed. * This function is a workaround for the fact that ImageMagick and/or GD does not compress GIF-files to their minimun size (that is RLE or no compression used) * * The function takes a file-reference, $theFile, and saves it again through GD or ImageMagick in order to compress the file * GIF: * If $type is not set, the compression is done with ImageMagick (provided that $GLOBALS['TYPO3_CONF_VARS']['GFX']['im_path_lzw'] is pointing to the path of a lzw-enabled version of 'convert') else with GD (should be RLE-enabled!) * If $type is set to either 'IM' or 'GD' the compression is done with ImageMagick and GD respectively * PNG: * No changes. * * $theFile is expected to be a valid GIF-file! * The function returns a code for the operation. * * @param string $theFile Filepath * @param string $type See description of function * @return string Returns "GD" if GD was used, otherwise "IM" if ImageMagick was used. If nothing done at all, it returns empty string. */ public static function gif_compress($theFile, $type) { $gfxConf = $GLOBALS['TYPO3_CONF_VARS']['GFX']; $returnCode = ''; if ($gfxConf['gif_compress'] && strtolower(substr($theFile, -4, 4)) == '.gif') { // GIF... if (($type == 'IM' || !$type) && $gfxConf['im'] && $gfxConf['im_path_lzw']) { // IM // use temporary file to prevent problems with read and write lock on same file on network file systems $temporaryName = dirname($theFile) . '/' . md5(uniqid()) . '.gif'; // rename could fail, if a simultaneous thread is currently working on the same thing if (@rename($theFile, $temporaryName)) { $cmd = self::imageMagickCommand('convert', '"' . $temporaryName . '" "' . $theFile . '"', $gfxConf['im_path_lzw']); t3lib_utility_Command::exec($cmd); unlink($temporaryName); } $returnCode = 'IM'; if (@is_file($theFile)) { self::fixPermissions($theFile); } } elseif (($type == 'GD' || !$type) && $gfxConf['gdlib'] && !$gfxConf['gdlib_png']) { // GD $tempImage = imageCreateFromGif($theFile); imageGif($tempImage, $theFile); imageDestroy($tempImage); $returnCode = 'GD'; if (@is_file($theFile)) { self::fixPermissions($theFile); } } } return $returnCode; } /** * Converts a png file to gif. * This converts a png file to gif IF the FLAG $GLOBALS['TYPO3_CONF_VARS']['FE']['png_to_gif'] is set TRUE. * * @param string $theFile the filename with path * @return string new filename */ public static function png_to_gif_by_imagemagick($theFile) { if ($GLOBALS['TYPO3_CONF_VARS']['FE']['png_to_gif'] && $GLOBALS['TYPO3_CONF_VARS']['GFX']['im'] && $GLOBALS['TYPO3_CONF_VARS']['GFX']['im_path_lzw'] && strtolower(substr($theFile, -4, 4)) == '.png' && @is_file($theFile)) { // IM $newFile = substr($theFile, 0, -4) . '.gif'; $cmd = self::imageMagickCommand('convert', '"' . $theFile . '" "' . $newFile . '"', $GLOBALS['TYPO3_CONF_VARS']['GFX']['im_path_lzw']); t3lib_utility_Command::exec($cmd); $theFile = $newFile; if (@is_file($newFile)) { self::fixPermissions($newFile); } // unlink old file?? May be bad idea because TYPO3 would then recreate the file every time as // TYPO3 thinks the file is not generated because it's missing!! So do not unlink $theFile here!! } return $theFile; } /** * Returns filename of the png/gif version of the input file (which can be png or gif). * If input file type does not match the wanted output type a conversion is made and temp-filename returned. * * @param string $theFile Filepath of image file * @param boolean $output_png If set, then input file is converted to PNG, otherwise to GIF * @return string If the new image file exists, its filepath is returned */ public static function read_png_gif($theFile, $output_png = FALSE) { if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['im'] && @is_file($theFile)) { $ext = strtolower(substr($theFile, -4, 4)); if ( ((string) $ext == '.png' && $output_png) || ((string) $ext == '.gif' && !$output_png) ) { return $theFile; } else { $newFile = PATH_site . 'typo3temp/readPG_' . md5($theFile . '|' . filemtime($theFile)) . ($output_png ? '.png' : '.gif'); $cmd = self::imageMagickCommand('convert', '"' . $theFile . '" "' . $newFile . '"', $GLOBALS['TYPO3_CONF_VARS']['GFX']['im_path']); t3lib_utility_Command::exec($cmd); if (@is_file($newFile)) { self::fixPermissions($newFile); return $newFile; } } } } /************************* * * STRING FUNCTIONS * *************************/ /** * Truncates a string with appended/prepended "..." and takes current character set into consideration. * * @param string $string string to truncate * @param integer $chars must be an integer with an absolute value of at least 4. if negative the string is cropped from the right end. * @param string $appendString appendix to the truncated string * @return string cropped string */ public static function fixed_lgd_cs($string, $chars, $appendString = '...') { if (is_object($GLOBALS['LANG'])) { return $GLOBALS['LANG']->csConvObj->crop($GLOBALS['LANG']->charSet, $string, $chars, $appendString); } elseif (is_object($GLOBALS['TSFE'])) { $charSet = ($GLOBALS['TSFE']->renderCharset != '' ? $GLOBALS['TSFE']->renderCharset : $GLOBALS['TSFE']->defaultCharSet); return $GLOBALS['TSFE']->csConvObj->crop($charSet, $string, $chars, $appendString); } else { // this case should not happen $csConvObj = self::makeInstance('t3lib_cs'); return $csConvObj->crop('utf-8', $string, $chars, $appendString); } } /** * Breaks up a single line of text for emails * * @param string $str The string to break up * @param string $newlineChar The string to implode the broken lines with (default/typically \n) * @param integer $lineWidth The line width * @return string reformatted text * @deprecated since TYPO3 4.6, will be removed in TYPO3 6.0 - Use t3lib_utility_Mail::breakLinesForEmail() */ public static function breakLinesForEmail($str, $newlineChar = LF, $lineWidth = 76) { self::logDeprecatedFunction(); return t3lib_utility_Mail::breakLinesForEmail($str, $newlineChar, $lineWidth); } /** * Match IP number with list of numbers with wildcard * Dispatcher method for switching into specialised IPv4 and IPv6 methods. * * @param string $baseIP is the current remote IP address for instance, typ. REMOTE_ADDR * @param string $list is a comma-list of IP-addresses to match with. *-wildcard allowed instead of number, plus leaving out parts in the IP number is accepted as wildcard (eg. 192.168.*.* equals 192.168). If list is "*" no check is done and the function returns TRUE immediately. An empty list always returns FALSE. * @return boolean TRUE if an IP-mask from $list matches $baseIP */ public static function cmpIP($baseIP, $list) { $list = trim($list); if ($list === '') { return FALSE; } elseif ($list === '*') { return TRUE; } if (strpos($baseIP, ':') !== FALSE && self::validIPv6($baseIP)) { return self::cmpIPv6($baseIP, $list); } else { return self::cmpIPv4($baseIP, $list); } } /** * Match IPv4 number with list of numbers with wildcard * * @param string $baseIP is the current remote IP address for instance, typ. REMOTE_ADDR * @param string $list is a comma-list of IP-addresses to match with. *-wildcard allowed instead of number, plus leaving out parts in the IP number is accepted as wildcard (eg. 192.168.*.* equals 192.168), could also contain IPv6 addresses * @return boolean TRUE if an IP-mask from $list matches $baseIP */ public static function cmpIPv4($baseIP, $list) { $IPpartsReq = explode('.', $baseIP); if (count($IPpartsReq) == 4) { $values = self::trimExplode(',', $list, 1); foreach ($values as $test) { $testList = explode('/', $test); if (count($testList) == 2) { list($test, $mask) = $testList; } else { $mask = FALSE; } if (intval($mask)) { // "192.168.3.0/24" $lnet = ip2long($test); $lip = ip2long($baseIP); $binnet = str_pad(decbin($lnet), 32, '0', STR_PAD_LEFT); $firstpart = substr($binnet, 0, $mask); $binip = str_pad(decbin($lip), 32, '0', STR_PAD_LEFT); $firstip = substr($binip, 0, $mask); $yes = (strcmp($firstpart, $firstip) == 0); } else { // "192.168.*.*" $IPparts = explode('.', $test); $yes = 1; foreach ($IPparts as $index => $val) { $val = trim($val); if (($val !== '*') && ($IPpartsReq[$index] !== $val)) { $yes = 0; } } } if ($yes) { return TRUE; } } } return FALSE; } /** * Match IPv6 address with a list of IPv6 prefixes * * @param string $baseIP is the current remote IP address for instance * @param string $list is a comma-list of IPv6 prefixes, could also contain IPv4 addresses * @return boolean TRUE if an baseIP matches any prefix */ public static function cmpIPv6($baseIP, $list) { $success = FALSE; // Policy default: Deny connection $baseIP = self::normalizeIPv6($baseIP); $values = self::trimExplode(',', $list, 1); foreach ($values as $test) { $testList = explode('/', $test); if (count($testList) == 2) { list($test, $mask) = $testList; } else { $mask = FALSE; } if (self::validIPv6($test)) { $test = self::normalizeIPv6($test); $maskInt = intval($mask) ? intval($mask) : 128; if ($mask === '0') { // special case; /0 is an allowed mask - equals a wildcard $success = TRUE; } elseif ($maskInt == 128) { $success = ($test === $baseIP); } else { $testBin = self::IPv6Hex2Bin($test); $baseIPBin = self::IPv6Hex2Bin($baseIP); $success = TRUE; // modulo is 0 if this is a 8-bit-boundary $maskIntModulo = $maskInt % 8; $numFullCharactersUntilBoundary = intval($maskInt / 8); if (substr($testBin, 0, $numFullCharactersUntilBoundary) !== substr($baseIPBin, 0, $numFullCharactersUntilBoundary)) { $success = FALSE; } elseif ($maskIntModulo > 0) { // if not an 8-bit-boundary, check bits of last character $testLastBits = str_pad(decbin(ord(substr($testBin, $numFullCharactersUntilBoundary, 1))), 8, '0', STR_PAD_LEFT); $baseIPLastBits = str_pad(decbin(ord(substr($baseIPBin, $numFullCharactersUntilBoundary, 1))), 8, '0', STR_PAD_LEFT); if (strncmp($testLastBits, $baseIPLastBits, $maskIntModulo) != 0) { $success = FALSE; } } } } if ($success) { return TRUE; } } return FALSE; } /** * Transform a regular IPv6 address from hex-representation into binary * * @param string $hex IPv6 address in hex-presentation * @return string Binary representation (16 characters, 128 characters) * @see IPv6Bin2Hex() */ public static function IPv6Hex2Bin($hex) { // use PHP-function if PHP was compiled with IPv6-support if (defined('AF_INET6')) { $bin = inet_pton($hex); } else { $hex = self::normalizeIPv6($hex); $hex = str_replace(':', '', $hex); // Replace colon to nothing $bin = pack("H*" , $hex); } return $bin; } /** * Transform an IPv6 address from binary to hex-representation * * @param string $bin IPv6 address in hex-presentation * @return string Binary representation (16 characters, 128 characters) * @see IPv6Hex2Bin() */ public static function IPv6Bin2Hex($bin) { // use PHP-function if PHP was compiled with IPv6-support if (defined('AF_INET6')) { $hex = inet_ntop($bin); } else { $hex = unpack("H*" , $bin); $hex = chunk_split($hex[1], 4, ':'); // strip last colon (from chunk_split) $hex = substr($hex, 0, -1); // IPv6 is now in normalized form // compress it for easier handling and to match result from inet_ntop() $hex = self::compressIPv6($hex); } return $hex; } /** * Normalize an IPv6 address to full length * * @param string $address Given IPv6 address * @return string Normalized address * @see compressIPv6() */ public static function normalizeIPv6($address) { $normalizedAddress = ''; $stageOneAddress = ''; // according to RFC lowercase-representation is recommended $address = strtolower($address); // normalized representation has 39 characters (0000:0000:0000:0000:0000:0000:0000:0000) if (strlen($address) == 39) { // already in full expanded form return $address; } $chunks = explode('::', $address); // Count 2 if if address has hidden zero blocks if (count($chunks) == 2) { $chunksLeft = explode(':', $chunks[0]); $chunksRight = explode(':', $chunks[1]); $left = count($chunksLeft); $right = count($chunksRight); // Special case: leading zero-only blocks count to 1, should be 0 if ($left == 1 && strlen($chunksLeft[0]) == 0) { $left = 0; } $hiddenBlocks = 8 - ($left + $right); $hiddenPart = ''; $h = 0; while ($h < $hiddenBlocks) { $hiddenPart .= '0000:'; $h++; } if ($left == 0) { $stageOneAddress = $hiddenPart . $chunks[1]; } else { $stageOneAddress = $chunks[0] . ':' . $hiddenPart . $chunks[1]; } } else { $stageOneAddress = $address; } // normalize the blocks: $blocks = explode(':', $stageOneAddress); $divCounter = 0; foreach ($blocks as $block) { $tmpBlock = ''; $i = 0; $hiddenZeros = 4 - strlen($block); while ($i < $hiddenZeros) { $tmpBlock .= '0'; $i++; } $normalizedAddress .= $tmpBlock . $block; if ($divCounter < 7) { $normalizedAddress .= ':'; $divCounter++; } } return $normalizedAddress; } /** * Compress an IPv6 address to the shortest notation * * @param string $address Given IPv6 address * @return string Compressed address * @see normalizeIPv6() */ public static function compressIPv6($address) { // use PHP-function if PHP was compiled with IPv6-support if (defined('AF_INET6')) { $bin = inet_pton($address); $address = inet_ntop($bin); } else { $address = self::normalizeIPv6($address); // append one colon for easier handling // will be removed later $address .= ':'; // according to IPv6-notation the longest match // of a package of '0000:' may be replaced with ':' // (resulting in something like '1234::abcd') for ($counter = 8; $counter > 1; $counter--) { $search = str_repeat('0000:', $counter); if (($pos = strpos($address, $search)) !== FALSE) { $address = substr($address, 0, $pos) . ':' . substr($address, $pos + ($counter*5)); break; } } // up to 3 zeros in the first part may be removed $address = preg_replace('/^0{1,3}/', '', $address); // up to 3 zeros at the beginning of other parts may be removed $address = preg_replace('/:0{1,3}/', ':', $address); // strip last colon (from chunk_split) $address = substr($address, 0, -1); } return $address; } /** * Validate a given IP address. * * Possible format are IPv4 and IPv6. * * @param string $ip IP address to be tested * @return boolean TRUE if $ip is either of IPv4 or IPv6 format. */ public static function validIP($ip) { return (filter_var($ip, FILTER_VALIDATE_IP) !== FALSE); } /** * Validate a given IP address to the IPv4 address format. * * Example for possible format: 10.0.45.99 * * @param string $ip IP address to be tested * @return boolean TRUE if $ip is of IPv4 format. */ public static function validIPv4($ip) { return (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== FALSE); } /** * Validate a given IP address to the IPv6 address format. * * Example for possible format: 43FB::BB3F:A0A0:0 | ::1 * * @param string $ip IP address to be tested * @return boolean TRUE if $ip is of IPv6 format. */ public static function validIPv6($ip) { return (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== FALSE); } /** * Match fully qualified domain name with list of strings with wildcard * * @param string $baseHost A hostname or an IPv4/IPv6-address (will by reverse-resolved; typically REMOTE_ADDR) * @param string $list A comma-list of domain names to match with. *-wildcard allowed but cannot be part of a string, so it must match the full host name (eg. myhost.*.com => correct, myhost.*domain.com => wrong) * @return boolean TRUE if a domain name mask from $list matches $baseIP */ public static function cmpFQDN($baseHost, $list) { $baseHost = trim($baseHost); if (empty($baseHost)) { return FALSE; } if (self::validIPv4($baseHost) || self::validIPv6($baseHost)) { // resolve hostname // note: this is reverse-lookup and can be randomly set as soon as somebody is able to set // the reverse-DNS for his IP (security when for example used with REMOTE_ADDR) $baseHostName = gethostbyaddr($baseHost); if ($baseHostName === $baseHost) { // unable to resolve hostname return FALSE; } } else { $baseHostName = $baseHost; } $baseHostNameParts = explode('.', $baseHostName); $values = self::trimExplode(',', $list, 1); foreach ($values as $test) { $hostNameParts = explode('.', $test); // to match hostNameParts can only be shorter (in case of wildcards) or equal if (count($hostNameParts) > count($baseHostNameParts)) { continue; } $yes = TRUE; foreach ($hostNameParts as $index => $val) { $val = trim($val); if ($val === '*') { // wildcard valid for one or more hostname-parts $wildcardStart = $index + 1; // wildcard as last/only part always matches, otherwise perform recursive checks if ($wildcardStart < count($hostNameParts)) { $wildcardMatched = FALSE; $tempHostName = implode('.', array_slice($hostNameParts, $index + 1)); while (($wildcardStart < count($baseHostNameParts)) && (!$wildcardMatched)) { $tempBaseHostName = implode('.', array_slice($baseHostNameParts, $wildcardStart)); $wildcardMatched = self::cmpFQDN($tempBaseHostName, $tempHostName); $wildcardStart++; } if ($wildcardMatched) { // match found by recursive compare return TRUE; } else { $yes = FALSE; } } } elseif ($baseHostNameParts[$index] !== $val) { // in case of no match $yes = FALSE; } } if ($yes) { return TRUE; } } return FALSE; } /** * Checks if a given URL matches the host that currently handles this HTTP request. * Scheme, hostname and (optional) port of the given URL are compared. * * @param string $url: URL to compare with the TYPO3 request host * @return boolean Whether the URL matches the TYPO3 request host */ public static function isOnCurrentHost($url) { return (stripos($url . '/', self::getIndpEnv('TYPO3_REQUEST_HOST') . '/') === 0); } /** * Check for item in list * Check if an item exists in a comma-separated list of items. * * @param string $list comma-separated list of items (string) * @param string $item item to check for * @return boolean TRUE if $item is in $list */ public static function inList($list, $item) { return (strpos(',' . $list . ',', ',' . $item . ',') !== FALSE ? TRUE : FALSE); } /** * Removes an item from a comma-separated list of items. * * @param string $element element to remove * @param string $list comma-separated list of items (string) * @return string new comma-separated list of items */ public static function rmFromList($element, $list) { $items = explode(',', $list); foreach ($items as $k => $v) { if ($v == $element) { unset($items[$k]); } } return implode(',', $items); } /** * Expand a comma-separated list of integers with ranges (eg 1,3-5,7 becomes 1,3,4,5,7). * Ranges are limited to 1000 values per range. * * @param string $list comma-separated list of integers with ranges (string) * @return string new comma-separated list of items */ public static function expandList($list) { $items = explode(',', $list); $list = array(); foreach ($items as $item) { $range = explode('-', $item); if (isset($range[1])) { $runAwayBrake = 1000; for ($n = $range[0]; $n <= $range[1]; $n++) { $list[] = $n; $runAwayBrake--; if ($runAwayBrake <= 0) { break; } } } else { $list[] = $item; } } return implode(',', $list); } /** * Forces the integer $theInt into the boundaries of $min and $max. If the $theInt is 'FALSE' then the $zeroValue is applied. * * @param integer $theInt Input value * @param integer $min Lower limit * @param integer $max Higher limit * @param integer $zeroValue Default value if input is FALSE. * @return integer The input value forced into the boundaries of $min and $max * @deprecated since TYPO3 4.6, will be removed in TYPO3 6.0 - Use t3lib_utility_Math::forceIntegerInRange() instead */ public static function intInRange($theInt, $min, $max = 2000000000, $zeroValue = 0) { self::logDeprecatedFunction(); return t3lib_utility_Math::forceIntegerInRange($theInt, $min, $max, $zeroValue); } /** * Returns the $integer if greater than zero, otherwise returns zero. * * @param integer $theInt Integer string to process * @return integer * @deprecated since TYPO3 4.6, will be removed in TYPO3 6.0 - Use t3lib_utility_Math::convertToPositiveInteger() instead */ public static function intval_positive($theInt) { self::logDeprecatedFunction(); return t3lib_utility_Math::convertToPositiveInteger($theInt); } /** * Returns an integer from a three part version number, eg '4.12.3' -> 4012003 * * @param string $verNumberStr Version number on format x.x.x * @return integer Integer version of version number (where each part can count to 999) * @deprecated since TYPO3 4.6, will be removed in TYPO3 6.1 - Use t3lib_utility_VersionNumber::convertVersionNumberToInteger() instead */ public static function int_from_ver($verNumberStr) { // Deprecation log is activated only for TYPO3 4.7 and above if (t3lib_utility_VersionNumber::convertVersionNumberToInteger(TYPO3_version) >= 4007000) { self::logDeprecatedFunction(); } return t3lib_utility_VersionNumber::convertVersionNumberToInteger($verNumberStr); } /** * Returns TRUE if the current TYPO3 version (or compatibility version) is compatible to the input version * Notice that this function compares branches, not versions (4.0.1 would be > 4.0.0 although they use the same compat_version) * * @param string $verNumberStr Minimum branch number required (format x.y / e.g. "4.0" NOT "4.0.0"!) * @return boolean Returns TRUE if this setup is compatible with the provided version number * @todo Still needs a function to convert versions to branches */ public static function compat_version($verNumberStr) { $currVersionStr = $GLOBALS['TYPO3_CONF_VARS']['SYS']['compat_version'] ? $GLOBALS['TYPO3_CONF_VARS']['SYS']['compat_version'] : TYPO3_branch; if (t3lib_utility_VersionNumber::convertVersionNumberToInteger($currVersionStr) < t3lib_utility_VersionNumber::convertVersionNumberToInteger($verNumberStr)) { return FALSE; } else { return TRUE; } } /** * Makes a positive integer hash out of the first 7 chars from the md5 hash of the input * * @param string $str String to md5-hash * @return integer Returns 28bit integer-hash */ public static function md5int($str) { return hexdec(substr(md5($str), 0, 7)); } /** * Returns the first 10 positions of the MD5-hash (changed from 6 to 10 recently) * * @param string $input Input string to be md5-hashed * @param integer $len The string-length of the output * @return string Substring of the resulting md5-hash, being $len chars long (from beginning) */ public static function shortMD5($input, $len = 10) { return substr(md5($input), 0, $len); } /** * Returns a proper HMAC on a given input string and secret TYPO3 encryption key. * * @param string $input Input string to create HMAC from * @param string $additionalSecret additionalSecret to prevent hmac beeing used in a different context * @return string resulting (hexadecimal) HMAC currently with a length of 40 (HMAC-SHA-1) */ public static function hmac($input, $additionalSecret = '') { $hashAlgorithm = 'sha1'; $hashBlocksize = 64; $hmac = ''; $secret = $GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'] . $additionalSecret; if (extension_loaded('hash') && function_exists('hash_hmac') && function_exists('hash_algos') && in_array($hashAlgorithm, hash_algos())) { $hmac = hash_hmac($hashAlgorithm, $input, $secret); } else { // outer padding $opad = str_repeat(chr(0x5C), $hashBlocksize); // inner padding $ipad = str_repeat(chr(0x36), $hashBlocksize); if (strlen($secret) > $hashBlocksize) { // keys longer than block size are shorten $key = str_pad(pack('H*', call_user_func($hashAlgorithm, $secret)), $hashBlocksize, chr(0)); } else { // keys shorter than block size are zero-padded $key = str_pad($secret, $hashBlocksize, chr(0)); } $hmac = call_user_func($hashAlgorithm, ($key ^ $opad) . pack('H*', call_user_func($hashAlgorithm, ($key ^ $ipad) . $input))); } return $hmac; } /** * Takes comma-separated lists and arrays and removes all duplicates * If a value in the list is trim(empty), the value is ignored. * * @param string $in_list Accept multiple parameters which can be comma-separated lists of values and arrays. * @param mixed $secondParameter: Dummy field, which if set will show a warning! * @return string Returns the list without any duplicates of values, space around values are trimmed */ public static function uniqueList($in_list, $secondParameter = NULL) { if (is_array($in_list)) { throw new InvalidArgumentException( 'TYPO3 Fatal Error: t3lib_div::uniqueList() does NOT support array arguments anymore! Only string comma lists!', 1270853885 ); } if (isset($secondParameter)) { throw new InvalidArgumentException( 'TYPO3 Fatal Error: t3lib_div::uniqueList() does NOT support more than a single argument value anymore. You have specified more than one!', 1270853886 ); } return implode(',', array_unique(self::trimExplode(',', $in_list, 1))); } /** * Splits a reference to a file in 5 parts * * @param string $fileref Filename/filepath to be analysed * @return array Contains keys [path], [file], [filebody], [fileext], [realFileext] */ public static function split_fileref($fileref) { $reg = array(); if (preg_match('/(.*\/)(.*)$/', $fileref, $reg)) { $info['path'] = $reg[1]; $info['file'] = $reg[2]; } else { $info['path'] = ''; $info['file'] = $fileref; } $reg = ''; if (!is_dir($fileref) && preg_match('/(.*)\.([^\.]*$)/', $info['file'], $reg)) { $info['filebody'] = $reg[1]; $info['fileext'] = strtolower($reg[2]); $info['realFileext'] = $reg[2]; } else { $info['filebody'] = $info['file']; $info['fileext'] = ''; } reset($info); return $info; } /** * Returns the directory part of a path without trailing slash * If there is no dir-part, then an empty string is returned. * Behaviour: * * '/dir1/dir2/script.php' => '/dir1/dir2' * '/dir1/' => '/dir1' * 'dir1/script.php' => 'dir1' * 'd/script.php' => 'd' * '/script.php' => '' * '' => '' * * @param string $path Directory name / path * @return string Processed input value. See function description. */ public static function dirname($path) { $p = self::revExplode('/', $path, 2); return count($p) == 2 ? $p[0] : ''; } /** * Modifies a HTML Hex color by adding/subtracting $R,$G and $B integers * * @param string $color A hexadecimal color code, #xxxxxx * @param integer $R Offset value 0-255 * @param integer $G Offset value 0-255 * @param integer $B Offset value 0-255 * @return string A hexadecimal color code, #xxxxxx, modified according to input vars * @see modifyHTMLColorAll() */ public static function modifyHTMLColor($color, $R, $G, $B) { // This takes a hex-color (# included!) and adds $R, $G and $B to the HTML-color (format: #xxxxxx) and returns the new color $nR = t3lib_utility_Math::forceIntegerInRange(hexdec(substr($color, 1, 2)) + $R, 0, 255); $nG = t3lib_utility_Math::forceIntegerInRange(hexdec(substr($color, 3, 2)) + $G, 0, 255); $nB = t3lib_utility_Math::forceIntegerInRange(hexdec(substr($color, 5, 2)) + $B, 0, 255); return '#' . substr('0' . dechex($nR), -2) . substr('0' . dechex($nG), -2) . substr('0' . dechex($nB), -2); } /** * Modifies a HTML Hex color by adding/subtracting $all integer from all R/G/B channels * * @param string $color A hexadecimal color code, #xxxxxx * @param integer $all Offset value 0-255 for all three channels. * @return string A hexadecimal color code, #xxxxxx, modified according to input vars * @see modifyHTMLColor() */ public static function modifyHTMLColorAll($color, $all) { return self::modifyHTMLColor($color, $all, $all, $all); } /** * Tests if the input can be interpreted as integer. * * @param mixed $var Any input variable to test * @return boolean Returns TRUE if string is an integer * @deprecated since TYPO3 4.6, will be removed in TYPO3 6.0 - Use t3lib_utility_Math::canBeInterpretedAsInteger() instead */ public static function testInt($var) { self::logDeprecatedFunction(); return t3lib_utility_Math::canBeInterpretedAsInteger($var); } /** * Returns TRUE if the first part of $str matches the string $partStr * * @param string $str Full string to check * @param string $partStr Reference string which must be found as the "first part" of the full string * @return boolean TRUE if $partStr was found to be equal to the first part of $str */ public static function isFirstPartOfStr($str, $partStr) { return $partStr != '' && strpos((string) $str, (string) $partStr, 0) === 0; } /** * Formats the input integer $sizeInBytes as bytes/kilobytes/megabytes (-/K/M) * * @param integer $sizeInBytes Number of bytes to format. * @param string $labels Labels for bytes, kilo, mega and giga separated by vertical bar (|) and possibly encapsulated in "". Eg: " | K| M| G" (which is the default value) * @return string Formatted representation of the byte number, for output. */ public static function formatSize($sizeInBytes, $labels = '') { // Set labels: if (strlen($labels) == 0) { $labels = ' | K| M| G'; } else { $labels = str_replace('"', '', $labels); } $labelArr = explode('|', $labels); // Find size: if ($sizeInBytes > 900) { if ($sizeInBytes > 900000000) { // GB $val = $sizeInBytes / (1024 * 1024 * 1024); return number_format($val, (($val < 20) ? 1 : 0), '.', '') . $labelArr[3]; } elseif ($sizeInBytes > 900000) { // MB $val = $sizeInBytes / (1024 * 1024); return number_format($val, (($val < 20) ? 1 : 0), '.', '') . $labelArr[2]; } else { // KB $val = $sizeInBytes / (1024); return number_format($val, (($val < 20) ? 1 : 0), '.', '') . $labelArr[1]; } } else { // Bytes return $sizeInBytes . $labelArr[0]; } } /** * Returns microtime input to milliseconds * * @param string $microtime Microtime * @return integer Microtime input string converted to an integer (milliseconds) */ public static function convertMicrotime($microtime) { $parts = explode(' ', $microtime); return round(($parts[0] + $parts[1]) * 1000); } /** * This splits a string by the chars in $operators (typical /+-*) and returns an array with them in * * @param string $string Input string, eg "123 + 456 / 789 - 4" * @param string $operators Operators to split by, typically "/+-*" * @return array Array with operators and operands separated. * @see tslib_cObj::calc(), tslib_gifBuilder::calcOffset() */ public static function splitCalc($string, $operators) { $res = Array(); $sign = '+'; while ($string) { $valueLen = strcspn($string, $operators); $value = substr($string, 0, $valueLen); $res[] = Array($sign, trim($value)); $sign = substr($string, $valueLen, 1); $string = substr($string, $valueLen + 1); } reset($res); return $res; } /** * Calculates the input by +,-,*,/,%,^ with priority to + and - * * @param string $string Input string, eg "123 + 456 / 789 - 4" * @return integer Calculated value. Or error string. * @see calcParenthesis() * @deprecated since TYPO3 4.6, will be removed in TYPO3 6.0 - Use t3lib_utility_Math::calculateWithPriorityToAdditionAndSubtraction() instead */ public static function calcPriority($string) { self::logDeprecatedFunction(); return t3lib_utility_Math::calculateWithPriorityToAdditionAndSubtraction($string); } /** * Calculates the input with parenthesis levels * * @param string $string Input string, eg "(123 + 456) / 789 - 4" * @return integer Calculated value. Or error string. * @see calcPriority(), tslib_cObj::stdWrap() * @deprecated since TYPO3 4.6, will be removed in TYPO3 6.0 - Use t3lib_utility_Math::calculateWithParentheses() instead */ public static function calcParenthesis($string) { self::logDeprecatedFunction(); return t3lib_utility_Math::calculateWithParentheses($string); } /** * Inverse version of htmlspecialchars() * * @param string $value Value where >, <, " and & should be converted to regular chars. * @return string Converted result. */ public static function htmlspecialchars_decode($value) { $value = str_replace('>', '>', $value); $value = str_replace('<', '<', $value); $value = str_replace('"', '"', $value); $value = str_replace('&', '&', $value); return $value; } /** * Re-converts HTML entities if they have been converted by htmlspecialchars() * * @param string $str String which contains eg. "&amp;" which should stay "&". Or "&#1234;" to "Ӓ". Or "&#x1b;" to "" * @return string Converted result. */ public static function deHSCentities($str) { return preg_replace('/&([#[:alnum:]]*;)/', '&\1', $str); } /** * This function is used to escape any ' -characters when transferring text to JavaScript! * * @param string $string String to escape * @param boolean $extended If set, also backslashes are escaped. * @param string $char The character to escape, default is ' (single-quote) * @return string Processed input string */ public static function slashJS($string, $extended = FALSE, $char = "'") { if ($extended) { $string = str_replace("\\", "\\\\", $string); } return str_replace($char, "\\" . $char, $string); } /** * Version of rawurlencode() where all spaces (%20) are re-converted to space-characters. * Useful when passing text to JavaScript where you simply url-encode it to get around problems with syntax-errors, linebreaks etc. * * @param string $str String to raw-url-encode with spaces preserved * @return string Rawurlencoded result of input string, but with all %20 (space chars) converted to real spaces. */ public static function rawUrlEncodeJS($str) { return str_replace('%20', ' ', rawurlencode($str)); } /** * rawurlencode which preserves "/" chars * Useful when file paths should keep the "/" chars, but have all other special chars encoded. * * @param string $str Input string * @return string Output string */ public static function rawUrlEncodeFP($str) { return str_replace('%2F', '/', rawurlencode($str)); } /** * Checking syntax of input email address * * @param string $email Input string to evaluate * @return boolean Returns TRUE if the $email address (input string) is valid */ public static function validEmail($email) { // enforce maximum length to prevent libpcre recursion crash bug #52929 in PHP // fixed in PHP 5.3.4; length restriction per SMTP RFC 2821 if (strlen($email) > 320) { return FALSE; } require_once(PATH_typo3 . 'contrib/idna/idna_convert.class.php'); $IDN = new idna_convert(array('idn_version' => 2008)); return (filter_var($IDN->encode($email), FILTER_VALIDATE_EMAIL) !== FALSE); } /** * Checks if current e-mail sending method does not accept recipient/sender name * in a call to PHP mail() function. Windows version of mail() and mini_sendmail * program are known not to process such input correctly and they cause SMTP * errors. This function will return TRUE if current mail sending method has * problem with recipient name in recipient/sender argument for mail(). * * TODO: 4.3 should have additional configuration variable, which is combined * by || with the rest in this function. * * @return boolean TRUE if mail() does not accept recipient name */ public static function isBrokenEmailEnvironment() { return TYPO3_OS == 'WIN' || (FALSE !== strpos(ini_get('sendmail_path'), 'mini_sendmail')); } /** * Changes from/to arguments for mail() function to work in any environment. * * @param string $address Address to adjust * @return string Adjusted address * @see t3lib_::isBrokenEmailEnvironment() */ public static function normalizeMailAddress($address) { if (self::isBrokenEmailEnvironment() && FALSE !== ($pos1 = strrpos($address, '<'))) { $pos2 = strpos($address, '>', $pos1); $address = substr($address, $pos1 + 1, ($pos2 ? $pos2 : strlen($address)) - $pos1 - 1); } return $address; } /** * Formats a string for output between