[TASK] Encapsulate bootstrap base code
[Packages/TYPO3.CMS.git] / t3lib / class.t3lib_div.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 1999-2011 Kasper Skårhøj (kasperYYYY@typo3.com)
6 * All rights reserved
7 *
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 * A copy is found in the textfile GPL.txt and important notices to the license
17 * from the author is found in LICENSE.txt distributed with these scripts.
18 *
19 *
20 * This script is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
27
28 /**
29 * The legendary "t3lib_div" class - Miscellaneous functions for general purpose.
30 * Most of the functions do not relate specifically to TYPO3
31 * However a section of functions requires certain TYPO3 features available
32 * See comments in the source.
33 * You are encouraged to use this library in your own scripts!
34 *
35 * USE:
36 * The class is intended to be used without creating an instance of it.
37 * So: Don't instantiate - call functions with "t3lib_div::" prefixed the function name.
38 * So use t3lib_div::[method-name] to refer to the functions, eg. 't3lib_div::milliseconds()'
39 *
40 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
41 * @package TYPO3
42 * @subpackage t3lib
43 */
44 final class t3lib_div {
45
46 // Severity constants used by t3lib_div::sysLog()
47 const SYSLOG_SEVERITY_INFO = 0;
48 const SYSLOG_SEVERITY_NOTICE = 1;
49 const SYSLOG_SEVERITY_WARNING = 2;
50 const SYSLOG_SEVERITY_ERROR = 3;
51 const SYSLOG_SEVERITY_FATAL = 4;
52
53 /**
54 * Singleton instances returned by makeInstance, using the class names as
55 * array keys
56 *
57 * @var array<t3lib_Singleton>
58 */
59 protected static $singletonInstances = array();
60
61 /**
62 * Instances returned by makeInstance, using the class names as array keys
63 *
64 * @var array<array><object>
65 */
66 protected static $nonSingletonInstances = array();
67
68 /*************************
69 *
70 * GET/POST Variables
71 *
72 * Background:
73 * Input GET/POST variables in PHP may have their quotes escaped with "\" or not depending on configuration.
74 * TYPO3 has always converted quotes to BE escaped if the configuration told that they would not be so.
75 * But the clean solution is that quotes are never escaped and that is what the functions below offers.
76 * Eventually TYPO3 should provide this in the global space as well.
77 * In the transitional phase (or forever..?) we need to encourage EVERY to read and write GET/POST vars through the API functions below.
78 *
79 *************************/
80
81 /**
82 * Returns the 'GLOBAL' value of incoming data from POST or GET, with priority to POST (that is equalent to 'GP' order)
83 * Strips slashes from all output, both strings and arrays.
84 * To enhancement security in your scripts, please consider using t3lib_div::_GET or t3lib_div::_POST if you already
85 * know by which method your data is arriving to the scripts!
86 *
87 * @param string $var GET/POST var to return
88 * @return mixed POST var named $var and if not set, the GET var of the same name.
89 */
90 public static function _GP($var) {
91 if (empty($var)) {
92 return;
93 }
94 $value = isset($_POST[$var]) ? $_POST[$var] : $_GET[$var];
95 if (isset($value)) {
96 if (is_array($value)) {
97 self::stripSlashesOnArray($value);
98 } else {
99 $value = stripslashes($value);
100 }
101 }
102 return $value;
103 }
104
105 /**
106 * Returns the global arrays $_GET and $_POST merged with $_POST taking precedence.
107 *
108 * @param string $parameter Key (variable name) from GET or POST vars
109 * @return array Returns the GET vars merged recursively onto the POST vars.
110 */
111 public static function _GPmerged($parameter) {
112 $postParameter = (isset($_POST[$parameter]) && is_array($_POST[$parameter])) ? $_POST[$parameter] : array();
113 $getParameter = (isset($_GET[$parameter]) && is_array($_GET[$parameter])) ? $_GET[$parameter] : array();
114
115 $mergedParameters = self::array_merge_recursive_overrule($getParameter, $postParameter);
116 self::stripSlashesOnArray($mergedParameters);
117
118 return $mergedParameters;
119 }
120
121 /**
122 * Returns the global $_GET array (or value from) normalized to contain un-escaped values.
123 * ALWAYS use this API function to acquire the GET variables!
124 *
125 * @param string $var Optional pointer to value in GET array (basically name of GET var)
126 * @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!*
127 * @see _POST(), _GP(), _GETset()
128 */
129 public static function _GET($var = NULL) {
130 $value = ($var === NULL) ? $_GET : (empty($var) ? NULL : $_GET[$var]);
131 // Removes slashes since TYPO3 has added them regardless of magic_quotes setting.
132 if (isset($value)) {
133 if (is_array($value)) {
134 self::stripSlashesOnArray($value);
135 } else {
136 $value = stripslashes($value);
137 }
138 }
139 return $value;
140 }
141
142 /**
143 * Returns the global $_POST array (or value from) normalized to contain un-escaped values.
144 * ALWAYS use this API function to acquire the $_POST variables!
145 *
146 * @param string $var Optional pointer to value in POST array (basically name of POST var)
147 * @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!*
148 * @see _GET(), _GP()
149 */
150 public static function _POST($var = NULL) {
151 $value = ($var === NULL) ? $_POST : (empty($var) ? NULL : $_POST[$var]);
152 // Removes slashes since TYPO3 has added them regardless of magic_quotes setting.
153 if (isset($value)) {
154 if (is_array($value)) {
155 self::stripSlashesOnArray($value);
156 } else {
157 $value = stripslashes($value);
158 }
159 }
160 return $value;
161 }
162
163 /**
164 * Writes input value to $_GET.
165 *
166 * @param mixed $inputGet
167 * Array or single value to write to $_GET. Values should NOT be
168 * escaped at input time (but will be escaped before writing
169 * according to TYPO3 standards).
170 * @param string $key
171 * Alternative key; If set, this will not set the WHOLE GET array,
172 * but only the key in it specified by this value!
173 * You can specify to replace keys on deeper array levels by
174 * separating the keys with a pipe.
175 * Example: 'parentKey|childKey' will result in
176 * array('parentKey' => array('childKey' => $inputGet))
177 *
178 * @return void
179 */
180 public static function _GETset($inputGet, $key = '') {
181 // Adds slashes since TYPO3 standard currently is that slashes
182 // must be applied (regardless of magic_quotes setting)
183 if (is_array($inputGet)) {
184 self::addSlashesOnArray($inputGet);
185 } else {
186 $inputGet = addslashes($inputGet);
187 }
188
189 if ($key != '') {
190 if (strpos($key, '|') !== FALSE) {
191 $pieces = explode('|', $key);
192 $newGet = array();
193 $pointer =& $newGet;
194 foreach ($pieces as $piece) {
195 $pointer =& $pointer[$piece];
196 }
197 $pointer = $inputGet;
198 $mergedGet = self::array_merge_recursive_overrule(
199 $_GET, $newGet
200 );
201
202 $_GET = $mergedGet;
203 $GLOBALS['HTTP_GET_VARS'] = $mergedGet;
204 } else {
205 $_GET[$key] = $inputGet;
206 $GLOBALS['HTTP_GET_VARS'][$key] = $inputGet;
207 }
208 } elseif (is_array($inputGet)) {
209 $_GET = $inputGet;
210 $GLOBALS['HTTP_GET_VARS'] = $inputGet;
211 }
212 }
213
214 /**
215 * Wrapper for the RemoveXSS function.
216 * Removes potential XSS code from an input string.
217 *
218 * Using an external class by Travis Puderbaugh <kallahar@quickwired.com>
219 *
220 * @param string $string Input string
221 * @return string Input string with potential XSS code removed
222 */
223 public static function removeXSS($string) {
224 require_once(PATH_typo3 . 'contrib/RemoveXSS/RemoveXSS.php');
225 $string = RemoveXSS::process($string);
226 return $string;
227 }
228
229 /*************************
230 *
231 * IMAGE FUNCTIONS
232 *
233 *************************/
234
235 /**
236 * Compressing a GIF file if not already LZW compressed.
237 * 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)
238 *
239 * The function takes a file-reference, $theFile, and saves it again through GD or ImageMagick in order to compress the file
240 * GIF:
241 * 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!)
242 * If $type is set to either 'IM' or 'GD' the compression is done with ImageMagick and GD respectively
243 * PNG:
244 * No changes.
245 *
246 * $theFile is expected to be a valid GIF-file!
247 * The function returns a code for the operation.
248 *
249 * @param string $theFile Filepath
250 * @param string $type See description of function
251 * @return string Returns "GD" if GD was used, otherwise "IM" if ImageMagick was used. If nothing done at all, it returns empty string.
252 */
253 public static function gif_compress($theFile, $type) {
254 $gfxConf = $GLOBALS['TYPO3_CONF_VARS']['GFX'];
255 $returnCode = '';
256 // GIF...
257 if ($gfxConf['gif_compress'] && strtolower(substr($theFile, -4, 4)) == '.gif') {
258 // IM
259 if (($type == 'IM' || !$type) && $gfxConf['im'] && $gfxConf['im_path_lzw']) {
260 // Use temporary file to prevent problems with read and write lock on same file on network file systems
261 $temporaryName = dirname($theFile) . '/' . md5(uniqid()) . '.gif';
262 // Rename could fail, if a simultaneous thread is currently working on the same thing
263 if (@rename($theFile, $temporaryName)) {
264 $cmd = self::imageMagickCommand('convert', '"' . $temporaryName . '" "' . $theFile . '"', $gfxConf['im_path_lzw']);
265 t3lib_utility_Command::exec($cmd);
266 unlink($temporaryName);
267 }
268
269 $returnCode = 'IM';
270 if (@is_file($theFile)) {
271 self::fixPermissions($theFile);
272 }
273 } elseif (($type == 'GD' || !$type) && $gfxConf['gdlib'] && !$gfxConf['gdlib_png']) { // GD
274 $tempImage = imageCreateFromGif($theFile);
275 imageGif($tempImage, $theFile);
276 imageDestroy($tempImage);
277 $returnCode = 'GD';
278 if (@is_file($theFile)) {
279 self::fixPermissions($theFile);
280 }
281 }
282 }
283 return $returnCode;
284 }
285
286 /**
287 * Converts a png file to gif.
288 * This converts a png file to gif IF the FLAG $GLOBALS['TYPO3_CONF_VARS']['FE']['png_to_gif'] is set TRUE.
289 *
290 * @param string $theFile The filename with path
291 * @return string New filename
292 */
293 public static function png_to_gif_by_imagemagick($theFile) {
294 if ($GLOBALS['TYPO3_CONF_VARS']['FE']['png_to_gif']
295 && $GLOBALS['TYPO3_CONF_VARS']['GFX']['im']
296 && $GLOBALS['TYPO3_CONF_VARS']['GFX']['im_path_lzw']
297 && strtolower(substr($theFile, -4, 4)) == '.png'
298 && @is_file($theFile)) { // IM
299 $newFile = substr($theFile, 0, -4) . '.gif';
300 $cmd = self::imageMagickCommand('convert', '"' . $theFile . '" "' . $newFile . '"', $GLOBALS['TYPO3_CONF_VARS']['GFX']['im_path_lzw']);
301 t3lib_utility_Command::exec($cmd);
302 $theFile = $newFile;
303 if (@is_file($newFile)) {
304 self::fixPermissions($newFile);
305 }
306 // unlink old file?? May be bad idea because TYPO3 would then recreate the file every time as
307 // TYPO3 thinks the file is not generated because it's missing!! So do not unlink $theFile here!!
308 }
309 return $theFile;
310 }
311
312 /**
313 * Returns filename of the png/gif version of the input file (which can be png or gif).
314 * If input file type does not match the wanted output type a conversion is made and temp-filename returned.
315 *
316 * @param string $theFile Filepath of image file
317 * @param boolean $output_png If set, then input file is converted to PNG, otherwise to GIF
318 * @return string If the new image file exists, its filepath is returned
319 */
320 public static function read_png_gif($theFile, $output_png = FALSE) {
321 if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['im'] && @is_file($theFile)) {
322 $ext = strtolower(substr($theFile, -4, 4));
323 if (
324 ((string) $ext == '.png' && $output_png) ||
325 ((string) $ext == '.gif' && !$output_png)
326 ) {
327 return $theFile;
328 } else {
329 $newFile = PATH_site . 'typo3temp/readPG_' . md5($theFile . '|' . filemtime($theFile)) . ($output_png ? '.png' : '.gif');
330 $cmd = self::imageMagickCommand('convert', '"' . $theFile . '" "' . $newFile . '"', $GLOBALS['TYPO3_CONF_VARS']['GFX']['im_path']);
331 t3lib_utility_Command::exec($cmd);
332 if (@is_file($newFile)) {
333 self::fixPermissions($newFile);
334 return $newFile;
335 }
336 }
337 }
338 }
339
340 /*************************
341 *
342 * STRING FUNCTIONS
343 *
344 *************************/
345
346 /**
347 * Truncates a string with appended/prepended "..." and takes current character set into consideration.
348 *
349 * @param string $string String to truncate
350 * @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.
351 * @param string $appendString Appendix to the truncated string
352 * @return string Cropped string
353 */
354 public static function fixed_lgd_cs($string, $chars, $appendString = '...') {
355 if (is_object($GLOBALS['LANG'])) {
356 return $GLOBALS['LANG']->csConvObj->crop($GLOBALS['LANG']->charSet, $string, $chars, $appendString);
357 } elseif (is_object($GLOBALS['TSFE'])) {
358 $charSet = ($GLOBALS['TSFE']->renderCharset != '' ? $GLOBALS['TSFE']->renderCharset : $GLOBALS['TSFE']->defaultCharSet);
359 return $GLOBALS['TSFE']->csConvObj->crop($charSet, $string, $chars, $appendString);
360 } else {
361 // This case should not happen
362 $csConvObj = self::makeInstance('t3lib_cs');
363 return $csConvObj->crop('utf-8', $string, $chars, $appendString);
364 }
365 }
366
367 /**
368 * Match IP number with list of numbers with wildcard
369 * Dispatcher method for switching into specialised IPv4 and IPv6 methods.
370 *
371 * @param string $baseIP Is the current remote IP address for instance, typ. REMOTE_ADDR
372 * @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.
373 * @return boolean TRUE if an IP-mask from $list matches $baseIP
374 */
375 public static function cmpIP($baseIP, $list) {
376 $list = trim($list);
377 if ($list === '') {
378 return FALSE;
379 } elseif ($list === '*') {
380 return TRUE;
381 }
382 if (strpos($baseIP, ':') !== FALSE && self::validIPv6($baseIP)) {
383 return self::cmpIPv6($baseIP, $list);
384 } else {
385 return self::cmpIPv4($baseIP, $list);
386 }
387 }
388
389 /**
390 * Match IPv4 number with list of numbers with wildcard
391 *
392 * @param string $baseIP Is the current remote IP address for instance, typ. REMOTE_ADDR
393 * @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
394 * @return boolean TRUE if an IP-mask from $list matches $baseIP
395 */
396 public static function cmpIPv4($baseIP, $list) {
397 $IPpartsReq = explode('.', $baseIP);
398 if (count($IPpartsReq) == 4) {
399 $values = self::trimExplode(',', $list, 1);
400
401 foreach ($values as $test) {
402 $testList = explode('/', $test);
403 if (count($testList) == 2) {
404 list($test, $mask) = $testList;
405 } else {
406 $mask = FALSE;
407 }
408
409 if (intval($mask)) {
410 // "192.168.3.0/24"
411 $lnet = ip2long($test);
412 $lip = ip2long($baseIP);
413 $binnet = str_pad(decbin($lnet), 32, '0', STR_PAD_LEFT);
414 $firstpart = substr($binnet, 0, $mask);
415 $binip = str_pad(decbin($lip), 32, '0', STR_PAD_LEFT);
416 $firstip = substr($binip, 0, $mask);
417 $yes = (strcmp($firstpart, $firstip) == 0);
418 } else {
419 // "192.168.*.*"
420 $IPparts = explode('.', $test);
421 $yes = 1;
422 foreach ($IPparts as $index => $val) {
423 $val = trim($val);
424 if (($val !== '*') && ($IPpartsReq[$index] !== $val)) {
425 $yes = 0;
426 }
427 }
428 }
429 if ($yes) {
430 return TRUE;
431 }
432 }
433 }
434 return FALSE;
435 }
436
437 /**
438 * Match IPv6 address with a list of IPv6 prefixes
439 *
440 * @param string $baseIP Is the current remote IP address for instance
441 * @param string $list Is a comma-list of IPv6 prefixes, could also contain IPv4 addresses
442 * @return boolean TRUE If an baseIP matches any prefix
443 */
444 public static function cmpIPv6($baseIP, $list) {
445 // Policy default: Deny connection
446 $success = FALSE;
447 $baseIP = self::normalizeIPv6($baseIP);
448
449 $values = self::trimExplode(',', $list, 1);
450 foreach ($values as $test) {
451 $testList = explode('/', $test);
452 if (count($testList) == 2) {
453 list($test, $mask) = $testList;
454 } else {
455 $mask = FALSE;
456 }
457
458 if (self::validIPv6($test)) {
459 $test = self::normalizeIPv6($test);
460 $maskInt = intval($mask) ? intval($mask) : 128;
461 // Special case; /0 is an allowed mask - equals a wildcard
462 if ($mask === '0') {
463 $success = TRUE;
464 } elseif ($maskInt == 128) {
465 $success = ($test === $baseIP);
466 } else {
467 $testBin = self::IPv6Hex2Bin($test);
468 $baseIPBin = self::IPv6Hex2Bin($baseIP);
469 $success = TRUE;
470
471 // Modulo is 0 if this is a 8-bit-boundary
472 $maskIntModulo = $maskInt % 8;
473 $numFullCharactersUntilBoundary = intval($maskInt / 8);
474
475 if (substr($testBin, 0, $numFullCharactersUntilBoundary) !== substr($baseIPBin, 0, $numFullCharactersUntilBoundary)) {
476 $success = FALSE;
477 } elseif ($maskIntModulo > 0) {
478 // If not an 8-bit-boundary, check bits of last character
479 $testLastBits = str_pad(decbin(ord(substr($testBin, $numFullCharactersUntilBoundary, 1))), 8, '0', STR_PAD_LEFT);
480 $baseIPLastBits = str_pad(decbin(ord(substr($baseIPBin, $numFullCharactersUntilBoundary, 1))), 8, '0', STR_PAD_LEFT);
481 if (strncmp($testLastBits, $baseIPLastBits, $maskIntModulo) != 0) {
482 $success = FALSE;
483 }
484 }
485 }
486 }
487 if ($success) {
488 return TRUE;
489 }
490 }
491 return FALSE;
492 }
493
494 /**
495 * Transform a regular IPv6 address from hex-representation into binary
496 *
497 * @param string $hex IPv6 address in hex-presentation
498 * @return string Binary representation (16 characters, 128 characters)
499 * @see IPv6Bin2Hex()
500 */
501 public static function IPv6Hex2Bin($hex) {
502 // Use PHP-function if PHP was compiled with IPv6-support
503 if (defined('AF_INET6')) {
504 $bin = inet_pton($hex);
505 } else {
506 $hex = self::normalizeIPv6($hex);
507 // Replace colon to nothing
508 $hex = str_replace(':', '', $hex);
509 $bin = pack('H*', $hex);
510 }
511 return $bin;
512 }
513
514 /**
515 * Transform an IPv6 address from binary to hex-representation
516 *
517 * @param string $bin IPv6 address in hex-presentation
518 * @return string Binary representation (16 characters, 128 characters)
519 * @see IPv6Hex2Bin()
520 */
521 public static function IPv6Bin2Hex($bin) {
522 // Use PHP-function if PHP was compiled with IPv6-support
523 if (defined('AF_INET6')) {
524 $hex = inet_ntop($bin);
525 } else {
526 $hex = unpack('H*', $bin);
527 $hex = chunk_split($hex[1], 4, ':');
528 // Strip last colon (from chunk_split)
529 $hex = substr($hex, 0, -1);
530 // IPv6 is now in normalized form
531 // Compress it for easier handling and to match result from inet_ntop()
532 $hex = self::compressIPv6($hex);
533 }
534 return $hex;
535
536 }
537
538 /**
539 * Normalize an IPv6 address to full length
540 *
541 * @param string $address Given IPv6 address
542 * @return string Normalized address
543 * @see compressIPv6()
544 */
545 public static function normalizeIPv6($address) {
546 $normalizedAddress = '';
547 $stageOneAddress = '';
548
549 // According to RFC lowercase-representation is recommended
550 $address = strtolower($address);
551
552 // Normalized representation has 39 characters (0000:0000:0000:0000:0000:0000:0000:0000)
553 if (strlen($address) == 39) {
554 // Already in full expanded form
555 return $address;
556 }
557
558 // Count 2 if if address has hidden zero blocks
559 $chunks = explode('::', $address);
560 if (count($chunks) == 2) {
561 $chunksLeft = explode(':', $chunks[0]);
562 $chunksRight = explode(':', $chunks[1]);
563 $left = count($chunksLeft);
564 $right = count($chunksRight);
565
566 // Special case: leading zero-only blocks count to 1, should be 0
567 if ($left == 1 && strlen($chunksLeft[0]) == 0) {
568 $left = 0;
569 }
570
571 $hiddenBlocks = 8 - ($left + $right);
572 $hiddenPart = '';
573 $h = 0;
574 while ($h < $hiddenBlocks) {
575 $hiddenPart .= '0000:';
576 $h++;
577 }
578
579 if ($left == 0) {
580 $stageOneAddress = $hiddenPart . $chunks[1];
581 } else {
582 $stageOneAddress = $chunks[0] . ':' . $hiddenPart . $chunks[1];
583 }
584 } else {
585 $stageOneAddress = $address;
586 }
587
588 // Normalize the blocks:
589 $blocks = explode(':', $stageOneAddress);
590 $divCounter = 0;
591 foreach ($blocks as $block) {
592 $tmpBlock = '';
593 $i = 0;
594 $hiddenZeros = 4 - strlen($block);
595 while ($i < $hiddenZeros) {
596 $tmpBlock .= '0';
597 $i++;
598 }
599 $normalizedAddress .= $tmpBlock . $block;
600 if ($divCounter < 7) {
601 $normalizedAddress .= ':';
602 $divCounter++;
603 }
604 }
605 return $normalizedAddress;
606 }
607
608
609 /**
610 * Compress an IPv6 address to the shortest notation
611 *
612 * @param string $address Given IPv6 address
613 * @return string Compressed address
614 * @see normalizeIPv6()
615 */
616 public static function compressIPv6($address) {
617 // Use PHP-function if PHP was compiled with IPv6-support
618 if (defined('AF_INET6')) {
619 $bin = inet_pton($address);
620 $address = inet_ntop($bin);
621 } else {
622 $address = self::normalizeIPv6($address);
623
624 // Append one colon for easier handling
625 // will be removed later
626 $address .= ':';
627
628 // According to IPv6-notation the longest match
629 // of a package of '0000:' may be replaced with ':'
630 // (resulting in something like '1234::abcd')
631 for ($counter = 8; $counter > 1; $counter--) {
632 $search = str_repeat('0000:', $counter);
633 if (($pos = strpos($address, $search)) !== FALSE) {
634 $address = substr($address, 0, $pos) . ':' . substr($address, $pos + ($counter*5));
635 break;
636 }
637 }
638
639 // Up to 3 zeros in the first part may be removed
640 $address = preg_replace('/^0{1,3}/', '', $address);
641 // Up to 3 zeros at the beginning of other parts may be removed
642 $address = preg_replace('/:0{1,3}/', ':', $address);
643
644 // Strip last colon (from chunk_split)
645 $address = substr($address, 0, -1);
646 }
647 return $address;
648 }
649
650 /**
651 * Validate a given IP address.
652 *
653 * Possible format are IPv4 and IPv6.
654 *
655 * @param string $ip IP address to be tested
656 * @return boolean TRUE if $ip is either of IPv4 or IPv6 format.
657 */
658 public static function validIP($ip) {
659 return (filter_var($ip, FILTER_VALIDATE_IP) !== FALSE);
660 }
661
662 /**
663 * Validate a given IP address to the IPv4 address format.
664 *
665 * Example for possible format: 10.0.45.99
666 *
667 * @param string $ip IP address to be tested
668 * @return boolean TRUE if $ip is of IPv4 format.
669 */
670 public static function validIPv4($ip) {
671 return (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== FALSE);
672 }
673
674 /**
675 * Validate a given IP address to the IPv6 address format.
676 *
677 * Example for possible format: 43FB::BB3F:A0A0:0 | ::1
678 *
679 * @param string $ip IP address to be tested
680 * @return boolean TRUE if $ip is of IPv6 format.
681 */
682 public static function validIPv6($ip) {
683 return (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== FALSE);
684 }
685
686 /**
687 * Match fully qualified domain name with list of strings with wildcard
688 *
689 * @param string $baseHost A hostname or an IPv4/IPv6-address (will by reverse-resolved; typically REMOTE_ADDR)
690 * @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)
691 * @return boolean TRUE if a domain name mask from $list matches $baseIP
692 */
693 public static function cmpFQDN($baseHost, $list) {
694 $baseHost = trim($baseHost);
695 if (empty($baseHost)) {
696 return FALSE;
697 }
698 if (self::validIPv4($baseHost) || self::validIPv6($baseHost)) {
699 // Resolve hostname
700 // Note: this is reverse-lookup and can be randomly set as soon as somebody is able to set
701 // the reverse-DNS for his IP (security when for example used with REMOTE_ADDR)
702 $baseHostName = gethostbyaddr($baseHost);
703 if ($baseHostName === $baseHost) {
704 // Unable to resolve hostname
705 return FALSE;
706 }
707 } else {
708 $baseHostName = $baseHost;
709 }
710 $baseHostNameParts = explode('.', $baseHostName);
711
712 $values = self::trimExplode(',', $list, 1);
713
714 foreach ($values as $test) {
715 $hostNameParts = explode('.', $test);
716
717 // To match hostNameParts can only be shorter (in case of wildcards) or equal
718 if (count($hostNameParts) > count($baseHostNameParts)) {
719 continue;
720 }
721
722 $yes = TRUE;
723 foreach ($hostNameParts as $index => $val) {
724 $val = trim($val);
725 if ($val === '*') {
726 // Wildcard valid for one or more hostname-parts
727
728 $wildcardStart = $index + 1;
729 // Wildcard as last/only part always matches, otherwise perform recursive checks
730 if ($wildcardStart < count($hostNameParts)) {
731 $wildcardMatched = FALSE;
732 $tempHostName = implode('.', array_slice($hostNameParts, $index + 1));
733 while (($wildcardStart < count($baseHostNameParts)) && (!$wildcardMatched)) {
734 $tempBaseHostName = implode('.', array_slice($baseHostNameParts, $wildcardStart));
735 $wildcardMatched = self::cmpFQDN($tempBaseHostName, $tempHostName);
736 $wildcardStart++;
737 }
738 if ($wildcardMatched) {
739 // Match found by recursive compare
740 return TRUE;
741 } else {
742 $yes = FALSE;
743 }
744 }
745 } elseif ($baseHostNameParts[$index] !== $val) {
746 // In case of no match
747 $yes = FALSE;
748 }
749 }
750 if ($yes) {
751 return TRUE;
752 }
753 }
754 return FALSE;
755 }
756
757 /**
758 * Checks if a given URL matches the host that currently handles this HTTP request.
759 * Scheme, hostname and (optional) port of the given URL are compared.
760 *
761 * @param string $url URL to compare with the TYPO3 request host
762 * @return boolean Whether the URL matches the TYPO3 request host
763 */
764 public static function isOnCurrentHost($url) {
765 return (stripos($url . '/', self::getIndpEnv('TYPO3_REQUEST_HOST') . '/') === 0);
766 }
767
768 /**
769 * Check for item in list
770 * Check if an item exists in a comma-separated list of items.
771 *
772 * @param string $list Comma-separated list of items (string)
773 * @param string $item Item to check for
774 * @return boolean TRUE if $item is in $list
775 */
776 public static function inList($list, $item) {
777 return (strpos(',' . $list . ',', ',' . $item . ',') !== FALSE ? TRUE : FALSE);
778 }
779
780 /**
781 * Removes an item from a comma-separated list of items.
782 *
783 * @param string $element Element to remove
784 * @param string $list Comma-separated list of items (string)
785 * @return string New comma-separated list of items
786 */
787 public static function rmFromList($element, $list) {
788 $items = explode(',', $list);
789 foreach ($items as $k => $v) {
790 if ($v == $element) {
791 unset($items[$k]);
792 }
793 }
794 return implode(',', $items);
795 }
796
797 /**
798 * Expand a comma-separated list of integers with ranges (eg 1,3-5,7 becomes 1,3,4,5,7).
799 * Ranges are limited to 1000 values per range.
800 *
801 * @param string $list Comma-separated list of integers with ranges (string)
802 * @return string New comma-separated list of items
803 */
804 public static function expandList($list) {
805 $items = explode(',', $list);
806 $list = array();
807 foreach ($items as $item) {
808 $range = explode('-', $item);
809 if (isset($range[1])) {
810 $runAwayBrake = 1000;
811 for ($n = $range[0]; $n <= $range[1]; $n++) {
812 $list[] = $n;
813
814 $runAwayBrake--;
815 if ($runAwayBrake <= 0) {
816 break;
817 }
818 }
819 } else {
820 $list[] = $item;
821 }
822 }
823 return implode(',', $list);
824 }
825
826 /**
827 * Returns an integer from a three part version number, eg '4.12.3' -> 4012003
828 *
829 * @param string $verNumberStr Version number on format x.x.x
830 * @return integer Integer version of version number (where each part can count to 999)
831 * @deprecated since TYPO3 4.6, will be removed in TYPO3 4.9 - Use t3lib_utility_VersionNumber::convertVersionNumberToInteger() instead
832 */
833 public static function int_from_ver($verNumberStr) {
834 // Deprecation log is activated only for TYPO3 4.7 and above
835 if (t3lib_utility_VersionNumber::convertVersionNumberToInteger(TYPO3_version) >= 4007000) {
836 self::logDeprecatedFunction();
837 }
838 return t3lib_utility_VersionNumber::convertVersionNumberToInteger($verNumberStr);
839 }
840
841 /**
842 * Returns TRUE if the current TYPO3 version (or compatibility version) is compatible to the input version
843 * Notice that this function compares branches, not versions (4.0.1 would be > 4.0.0 although they use the same compat_version)
844 *
845 * @param string $verNumberStr Minimum branch number required (format x.y / e.g. "4.0" NOT "4.0.0"!)
846 * @return boolean Returns TRUE if this setup is compatible with the provided version number
847 * @todo Still needs a function to convert versions to branches
848 */
849 public static function compat_version($verNumberStr) {
850 $currVersionStr = $GLOBALS['TYPO3_CONF_VARS']['SYS']['compat_version'] ? $GLOBALS['TYPO3_CONF_VARS']['SYS']['compat_version'] : TYPO3_branch;
851
852 if (t3lib_utility_VersionNumber::convertVersionNumberToInteger($currVersionStr) < t3lib_utility_VersionNumber::convertVersionNumberToInteger($verNumberStr)) {
853 return FALSE;
854 } else {
855 return TRUE;
856 }
857 }
858
859 /**
860 * Makes a positive integer hash out of the first 7 chars from the md5 hash of the input
861 *
862 * @param string $str String to md5-hash
863 * @return integer Returns 28bit integer-hash
864 */
865 public static function md5int($str) {
866 return hexdec(substr(md5($str), 0, 7));
867 }
868
869 /**
870 * Returns the first 10 positions of the MD5-hash (changed from 6 to 10 recently)
871 *
872 * @param string $input Input string to be md5-hashed
873 * @param integer $len The string-length of the output
874 * @return string Substring of the resulting md5-hash, being $len chars long (from beginning)
875 */
876 public static function shortMD5($input, $len = 10) {
877 return substr(md5($input), 0, $len);
878 }
879
880 /**
881 * Returns a proper HMAC on a given input string and secret TYPO3 encryption key.
882 *
883 * @param string $input Input string to create HMAC from
884 * @return string resulting (hexadecimal) HMAC currently with a length of 40 (HMAC-SHA-1)
885 */
886 public static function hmac($input) {
887 $hashAlgorithm = 'sha1';
888 $hashBlocksize = 64;
889 $hmac = '';
890
891 if (extension_loaded('hash') && function_exists('hash_hmac') && function_exists('hash_algos') && in_array($hashAlgorithm, hash_algos())) {
892 $hmac = hash_hmac($hashAlgorithm, $input, $GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey']);
893 } else {
894 // Outer padding
895 $opad = str_repeat(chr(0x5C), $hashBlocksize);
896 // Inner padding
897 $ipad = str_repeat(chr(0x36), $hashBlocksize);
898 if (strlen($GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey']) > $hashBlocksize) {
899 // Keys longer than block size are shorten
900 $key = str_pad(pack('H*', call_user_func($hashAlgorithm, $GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'])), $hashBlocksize, chr(0x00));
901 } else {
902 // Keys shorter than block size are zero-padded
903 $key = str_pad($GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'], $hashBlocksize, chr(0x00));
904 }
905 $hmac = call_user_func($hashAlgorithm, ($key ^ $opad) . pack('H*', call_user_func($hashAlgorithm, ($key ^ $ipad) . $input)));
906 }
907 return $hmac;
908 }
909
910 /**
911 * Takes comma-separated lists and arrays and removes all duplicates
912 * If a value in the list is trim(empty), the value is ignored.
913 *
914 * @param string $in_list Accept multiple parameters which can be comma-separated lists of values and arrays.
915 * @param mixed $secondParameter Dummy field, which if set will show a warning!
916 * @return string Returns the list without any duplicates of values, space around values are trimmed
917 */
918 public static function uniqueList($in_list, $secondParameter = NULL) {
919 if (is_array($in_list)) {
920 throw new InvalidArgumentException(
921 'TYPO3 Fatal Error: t3lib_div::uniqueList() does NOT support array arguments anymore! Only string comma lists!',
922 1270853885
923 );
924 }
925 if (isset($secondParameter)) {
926 throw new InvalidArgumentException(
927 'TYPO3 Fatal Error: t3lib_div::uniqueList() does NOT support more than a single argument value anymore. You have specified more than one!',
928 1270853886
929 );
930 }
931
932 return implode(',', array_unique(self::trimExplode(',', $in_list, 1)));
933 }
934
935 /**
936 * Splits a reference to a file in 5 parts
937 *
938 * @param string $fileref Filename/filepath to be analysed
939 * @return array Contains keys [path], [file], [filebody], [fileext], [realFileext]
940 */
941 public static function split_fileref($fileref) {
942 $reg = array();
943 if (preg_match('/(.*\/)(.*)$/', $fileref, $reg)) {
944 $info['path'] = $reg[1];
945 $info['file'] = $reg[2];
946 } else {
947 $info['path'] = '';
948 $info['file'] = $fileref;
949 }
950
951 $reg = '';
952 if (!is_dir($fileref) && preg_match('/(.*)\.([^\.]*$)/', $info['file'], $reg)) {
953 $info['filebody'] = $reg[1];
954 $info['fileext'] = strtolower($reg[2]);
955 $info['realFileext'] = $reg[2];
956 } else {
957 $info['filebody'] = $info['file'];
958 $info['fileext'] = '';
959 }
960 reset($info);
961 return $info;
962 }
963
964 /**
965 * Returns the directory part of a path without trailing slash
966 * If there is no dir-part, then an empty string is returned.
967 * Behaviour:
968 *
969 * '/dir1/dir2/script.php' => '/dir1/dir2'
970 * '/dir1/' => '/dir1'
971 * 'dir1/script.php' => 'dir1'
972 * 'd/script.php' => 'd'
973 * '/script.php' => ''
974 * '' => ''
975 *
976 * @param string $path Directory name / path
977 * @return string Processed input value. See function description.
978 */
979 public static function dirname($path) {
980 $p = self::revExplode('/', $path, 2);
981 return count($p) == 2 ? $p[0] : '';
982 }
983
984 /**
985 * Modifies a HTML Hex color by adding/subtracting $R,$G and $B integers
986 *
987 * @param string $color A hexadecimal color code, #xxxxxx
988 * @param integer $R Offset value 0-255
989 * @param integer $G Offset value 0-255
990 * @param integer $B Offset value 0-255
991 * @return string A hexadecimal color code, #xxxxxx, modified according to input vars
992 * @see modifyHTMLColorAll()
993 */
994 public static function modifyHTMLColor($color, $R, $G, $B) {
995 // This takes a hex-color (# included!) and adds $R, $G and $B to the HTML-color (format: #xxxxxx) and returns the new color
996 $nR = t3lib_utility_Math::forceIntegerInRange(hexdec(substr($color, 1, 2)) + $R, 0, 255);
997 $nG = t3lib_utility_Math::forceIntegerInRange(hexdec(substr($color, 3, 2)) + $G, 0, 255);
998 $nB = t3lib_utility_Math::forceIntegerInRange(hexdec(substr($color, 5, 2)) + $B, 0, 255);
999 return '#' .
1000 substr('0' . dechex($nR), -2) .
1001 substr('0' . dechex($nG), -2) .
1002 substr('0' . dechex($nB), -2);
1003 }
1004
1005 /**
1006 * Modifies a HTML Hex color by adding/subtracting $all integer from all R/G/B channels
1007 *
1008 * @param string $color A hexadecimal color code, #xxxxxx
1009 * @param integer $all Offset value 0-255 for all three channels.
1010 * @return string A hexadecimal color code, #xxxxxx, modified according to input vars
1011 * @see modifyHTMLColor()
1012 */
1013 public static function modifyHTMLColorAll($color, $all) {
1014 return self::modifyHTMLColor($color, $all, $all, $all);
1015 }
1016
1017 /**
1018 * Returns TRUE if the first part of $str matches the string $partStr
1019 *
1020 * @param string $str Full string to check
1021 * @param string $partStr Reference string which must be found as the "first part" of the full string
1022 * @return boolean TRUE if $partStr was found to be equal to the first part of $str
1023 */
1024 public static function isFirstPartOfStr($str, $partStr) {
1025 return $partStr != '' && strpos((string) $str, (string) $partStr, 0) === 0;
1026 }
1027
1028 /**
1029 * Formats the input integer $sizeInBytes as bytes/kilobytes/megabytes (-/K/M)
1030 *
1031 * @param integer $sizeInBytes Number of bytes to format.
1032 * @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)
1033 * @return string Formatted representation of the byte number, for output.
1034 */
1035 public static function formatSize($sizeInBytes, $labels = '') {
1036
1037 // Set labels:
1038 if (strlen($labels) == 0) {
1039 $labels = ' | K| M| G';
1040 } else {
1041 $labels = str_replace('"', '', $labels);
1042 }
1043 $labelArr = explode('|', $labels);
1044
1045 // Find size:
1046 if ($sizeInBytes > 900) {
1047 // GB
1048 if ($sizeInBytes > 900000000) {
1049 $val = $sizeInBytes / (1024 * 1024 * 1024);
1050 return number_format($val, (($val < 20) ? 1 : 0), '.', '') . $labelArr[3];
1051 } elseif ($sizeInBytes > 900000) { // MB
1052 $val = $sizeInBytes / (1024 * 1024);
1053 return number_format($val, (($val < 20) ? 1 : 0), '.', '') . $labelArr[2];
1054 } else { // KB
1055 $val = $sizeInBytes / (1024);
1056 return number_format($val, (($val < 20) ? 1 : 0), '.', '') . $labelArr[1];
1057 }
1058 } else { // Bytes
1059 return $sizeInBytes . $labelArr[0];
1060 }
1061 }
1062
1063 /**
1064 * Returns microtime input to milliseconds
1065 *
1066 * @param string $microtime Microtime
1067 * @return integer Microtime input string converted to an integer (milliseconds)
1068 */
1069 public static function convertMicrotime($microtime) {
1070 $parts = explode(' ', $microtime);
1071 return round(($parts[0] + $parts[1]) * 1000);
1072 }
1073
1074 /**
1075 * This splits a string by the chars in $operators (typical /+-*) and returns an array with them in
1076 *
1077 * @param string $string Input string, eg "123 + 456 / 789 - 4"
1078 * @param string $operators Operators to split by, typically "/+-*"
1079 * @return array Array with operators and operands separated.
1080 * @see tslib_cObj::calc(), tslib_gifBuilder::calcOffset()
1081 */
1082 public static function splitCalc($string, $operators) {
1083 $res = Array();
1084 $sign = '+';
1085 while ($string) {
1086 $valueLen = strcspn($string, $operators);
1087 $value = substr($string, 0, $valueLen);
1088 $res[] = Array($sign, trim($value));
1089 $sign = substr($string, $valueLen, 1);
1090 $string = substr($string, $valueLen + 1);
1091 }
1092 reset($res);
1093 return $res;
1094 }
1095
1096 /**
1097 * Inverse version of htmlspecialchars()
1098 *
1099 * @param string $value Value where &gt;, &lt;, &quot; and &amp; should be converted to regular chars.
1100 * @return string Converted result.
1101 */
1102 public static function htmlspecialchars_decode($value) {
1103 $value = str_replace('&gt;', '>', $value);
1104 $value = str_replace('&lt;', '<', $value);
1105 $value = str_replace('&quot;', '"', $value);
1106 $value = str_replace('&amp;', '&', $value);
1107 return $value;
1108 }
1109
1110 /**
1111 * Re-converts HTML entities if they have been converted by htmlspecialchars()
1112 *
1113 * @param string $str String which contains eg. "&amp;amp;" which should stay "&amp;". Or "&amp;#1234;" to "&#1234;". Or "&amp;#x1b;" to "&#x1b;"
1114 * @return string Converted result.
1115 */
1116 public static function deHSCentities($str) {
1117 return preg_replace('/&amp;([#[:alnum:]]*;)/', '&\1', $str);
1118 }
1119
1120 /**
1121 * This function is used to escape any ' -characters when transferring text to JavaScript!
1122 *
1123 * @param string $string String to escape
1124 * @param boolean $extended If set, also backslashes are escaped.
1125 * @param string $char The character to escape, default is ' (single-quote)
1126 * @return string Processed input string
1127 */
1128 public static function slashJS($string, $extended = FALSE, $char = "'") {
1129 if ($extended) {
1130 $string = str_replace('\\', '\\\\', $string);
1131 }
1132 return str_replace($char, '\\' . $char, $string);
1133 }
1134
1135 /**
1136 * Version of rawurlencode() where all spaces (%20) are re-converted to space-characters.
1137 * Useful when passing text to JavaScript where you simply url-encode it to get around problems with syntax-errors, linebreaks etc.
1138 *
1139 * @param string $str String to raw-url-encode with spaces preserved
1140 * @return string Rawurlencoded result of input string, but with all %20 (space chars) converted to real spaces.
1141 */
1142 public static function rawUrlEncodeJS($str) {
1143 return str_replace('%20', ' ', rawurlencode($str));
1144 }
1145
1146 /**
1147 * rawurlencode which preserves "/" chars
1148 * Useful when file paths should keep the "/" chars, but have all other special chars encoded.
1149 *
1150 * @param string $str Input string
1151 * @return string Output string
1152 */
1153 public static function rawUrlEncodeFP($str) {
1154 return str_replace('%2F', '/', rawurlencode($str));
1155 }
1156
1157 /**
1158 * Checking syntax of input email address
1159 *
1160 * @param string $email Input string to evaluate
1161 * @return boolean Returns TRUE if the $email address (input string) is valid
1162 */
1163 public static function validEmail($email) {
1164 // Enforce maximum length to prevent libpcre recursion crash bug #52929 in PHP
1165 // fixed in PHP 5.3.4; length restriction per SMTP RFC 2821
1166 if (strlen($email) > 320) {
1167 return FALSE;
1168 }
1169 require_once(PATH_typo3 . 'contrib/idna/idna_convert.class.php');
1170 $IDN = new idna_convert(array('idn_version' => 2008));
1171
1172 return (filter_var($IDN->encode($email), FILTER_VALIDATE_EMAIL) !== FALSE);
1173 }
1174
1175 /**
1176 * Checks if current e-mail sending method does not accept recipient/sender name
1177 * in a call to PHP mail() function. Windows version of mail() and mini_sendmail
1178 * program are known not to process such input correctly and they cause SMTP
1179 * errors. This function will return TRUE if current mail sending method has
1180 * problem with recipient name in recipient/sender argument for mail().
1181 *
1182 * TODO: 4.3 should have additional configuration variable, which is combined
1183 * by || with the rest in this function.
1184 *
1185 * @return boolean TRUE if mail() does not accept recipient name
1186 */
1187 public static function isBrokenEmailEnvironment() {
1188 return TYPO3_OS == 'WIN' || (FALSE !== strpos(ini_get('sendmail_path'), 'mini_sendmail'));
1189 }
1190
1191 /**
1192 * Changes from/to arguments for mail() function to work in any environment.
1193 *
1194 * @param string $address Address to adjust
1195 * @return string Adjusted address
1196 * @see t3lib_::isBrokenEmailEnvironment()
1197 */
1198 public static function normalizeMailAddress($address) {
1199 if (self::isBrokenEmailEnvironment() && FALSE !== ($pos1 = strrpos($address, '<'))) {
1200 $pos2 = strpos($address, '>', $pos1);
1201 $address = substr($address, $pos1 + 1, ($pos2 ? $pos2 : strlen($address)) - $pos1 - 1);
1202 }
1203 return $address;
1204 }
1205
1206 /**
1207 * Formats a string for output between <textarea>-tags
1208 * All content outputted in a textarea form should be passed through this function
1209 * Not only is the content htmlspecialchar'ed on output but there is also a single newline added in the top. The newline is necessary because browsers will ignore the first newline after <textarea> if that is the first character. Therefore better set it!
1210 *
1211 * @param string $content Input string to be formatted.
1212 * @return string Formatted for <textarea>-tags
1213 */
1214 public static function formatForTextarea($content) {
1215 return LF . htmlspecialchars($content);
1216 }
1217
1218 /**
1219 * Converts string to uppercase
1220 * The function converts all Latin characters (a-z, but no accents, etc) to
1221 * uppercase. It is safe for all supported character sets (incl. utf-8).
1222 * Unlike strtoupper() it does not honour the locale.
1223 *
1224 * @param string $str Input string
1225 * @return string Uppercase String
1226 */
1227 public static function strtoupper($str) {
1228 return strtr((string) $str, 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ');
1229 }
1230
1231 /**
1232 * Converts string to lowercase
1233 * The function converts all Latin characters (A-Z, but no accents, etc) to
1234 * lowercase. It is safe for all supported character sets (incl. utf-8).
1235 * Unlike strtolower() it does not honour the locale.
1236 *
1237 * @param string $str Input string
1238 * @return string Lowercase String
1239 */
1240 public static function strtolower($str) {
1241 return strtr((string) $str, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz');
1242 }
1243
1244 /**
1245 * Returns a string of highly randomized bytes (over the full 8-bit range).
1246 *
1247 * Note: Returned values are not guaranteed to be crypto-safe,
1248 * most likely they are not, depending on the used retrieval method.
1249 *
1250 * @param integer $bytesToReturn Number of characters (bytes) to return
1251 * @return string Random Bytes
1252 * @see http://bugs.php.net/bug.php?id=52523
1253 * @see http://www.php-security.org/2010/05/09/mops-submission-04-generating-unpredictable-session-ids-and-hashes/index.html
1254 */
1255 public static function generateRandomBytes($bytesToReturn) {
1256 // Cache 4k of the generated bytestream.
1257 static $bytes = '';
1258 $bytesToGenerate = max(4096, $bytesToReturn);
1259
1260 // if we have not enough random bytes cached, we generate new ones
1261 if (!isset($bytes{$bytesToReturn - 1})) {
1262 if (TYPO3_OS === 'WIN') {
1263 // Openssl seems to be deadly slow on Windows, so try to use mcrypt
1264 // Windows PHP versions have a bug when using urandom source (see #24410)
1265 $bytes .= self::generateRandomBytesMcrypt($bytesToGenerate, MCRYPT_RAND);
1266 } else {
1267 // Try to use native PHP functions first, precedence has openssl
1268 $bytes .= self::generateRandomBytesOpenSsl($bytesToGenerate);
1269
1270 if (!isset($bytes{$bytesToReturn - 1})) {
1271 $bytes .= self::generateRandomBytesMcrypt($bytesToGenerate, MCRYPT_DEV_URANDOM);
1272 }
1273
1274 // If openssl and mcrypt failed, try /dev/urandom
1275 if (!isset($bytes{$bytesToReturn - 1})) {
1276 $bytes .= self::generateRandomBytesUrandom($bytesToGenerate);
1277 }
1278 }
1279
1280 // Fall back if other random byte generation failed until now
1281 if (!isset($bytes{$bytesToReturn - 1})) {
1282 $bytes .= self::generateRandomBytesFallback($bytesToReturn);
1283 }
1284 }
1285
1286 // get first $bytesToReturn and remove it from the byte cache
1287 $output = substr($bytes, 0, $bytesToReturn);
1288 $bytes = substr($bytes, $bytesToReturn);
1289
1290 return $output;
1291 }
1292
1293 /**
1294 * Generate random bytes using openssl if available
1295 *
1296 * @param string $bytesToGenerate
1297 * @return string
1298 */
1299 protected static function generateRandomBytesOpenSsl($bytesToGenerate) {
1300 if (!function_exists('openssl_random_pseudo_bytes')) {
1301 return '';
1302 }
1303 $isStrong = NULL;
1304 return (string) openssl_random_pseudo_bytes($bytesToGenerate, $isStrong);
1305 }
1306
1307 /**
1308 * Generate random bytes using mcrypt if available
1309 *
1310 * @param $bytesToGenerate
1311 * @param $randomSource
1312 * @return string
1313 */
1314 protected static function generateRandomBytesMcrypt($bytesToGenerate, $randomSource) {
1315 if (!function_exists('mcrypt_create_iv')) {
1316 return '';
1317 }
1318 return (string) @mcrypt_create_iv($bytesToGenerate, $randomSource);
1319 }
1320
1321 /**
1322 * Read random bytes from /dev/urandom if it is accessible
1323 *
1324 * @param $bytesToGenerate
1325 * @return string
1326 */
1327 protected static function generateRandomBytesUrandom($bytesToGenerate) {
1328 $bytes = '';
1329 $fh = @fopen('/dev/urandom', 'rb');
1330 if ($fh) {
1331 // PHP only performs buffered reads, so in reality it will always read
1332 // at least 4096 bytes. Thus, it costs nothing extra to read and store
1333 // that much so as to speed any additional invocations.
1334 $bytes = fread($fh, $bytesToGenerate);
1335 fclose($fh);
1336 }
1337
1338 return $bytes;
1339 }
1340
1341 /**
1342 * Generate pseudo random bytes as last resort
1343 *
1344 * @param $bytesToReturn
1345 * @return string
1346 */
1347 protected static function generateRandomBytesFallback($bytesToReturn) {
1348 $bytes = '';
1349 // We initialize with somewhat random.
1350 $randomState = $GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'] . base_convert(memory_get_usage() % pow(10, 6), 10, 2) . microtime() . uniqid('') . getmypid();
1351 while (!isset($bytes{$bytesToReturn - 1})) {
1352 $randomState = sha1(microtime() . mt_rand() . $randomState);
1353 $bytes .= sha1(mt_rand() . $randomState, TRUE);
1354 }
1355 return $bytes;
1356 }
1357
1358 /**
1359 * Returns a hex representation of a random byte string.
1360 *
1361 * @param integer $count Number of hex characters to return
1362 * @return string Random Bytes
1363 */
1364 public static function getRandomHexString($count) {
1365 return substr(bin2hex(self::generateRandomBytes(intval(($count + 1) / 2))), 0, $count);
1366 }
1367
1368 /**
1369 * Returns a given string with underscores as UpperCamelCase.
1370 * Example: Converts blog_example to BlogExample
1371 *
1372 * @param string $string String to be converted to camel case
1373 * @return string UpperCamelCasedWord
1374 */
1375 public static function underscoredToUpperCamelCase($string) {
1376 $upperCamelCase = str_replace(' ', '', ucwords(str_replace('_', ' ', self::strtolower($string))));
1377 return $upperCamelCase;
1378 }
1379
1380 /**
1381 * Returns a given string with underscores as lowerCamelCase.
1382 * Example: Converts minimal_value to minimalValue
1383 *
1384 * @param string $string String to be converted to camel case
1385 * @return string lowerCamelCasedWord
1386 */
1387 public static function underscoredToLowerCamelCase($string) {
1388 $upperCamelCase = str_replace(' ', '', ucwords(str_replace('_', ' ', self::strtolower($string))));
1389 $lowerCamelCase = self::lcfirst($upperCamelCase);
1390 return $lowerCamelCase;
1391 }
1392
1393 /**
1394 * Returns a given CamelCasedString as an lowercase string with underscores.
1395 * Example: Converts BlogExample to blog_example, and minimalValue to minimal_value
1396 *
1397 * @param string $string String to be converted to lowercase underscore
1398 * @return string lowercase_and_underscored_string
1399 */
1400 public static function camelCaseToLowerCaseUnderscored($string) {
1401 return self::strtolower(preg_replace('/(?<=\w)([A-Z])/', '_\\1', $string));
1402 }
1403
1404 /**
1405 * Converts the first char of a string to lowercase if it is a latin character (A-Z).
1406 * Example: Converts "Hello World" to "hello World"
1407 *
1408 * @param string $string The string to be used to lowercase the first character
1409 * @return string The string with the first character as lowercase
1410 */
1411 public static function lcfirst($string) {
1412 return self::strtolower(substr($string, 0, 1)) . substr($string, 1);
1413 }
1414
1415 /**
1416 * Checks if a given string is a Uniform Resource Locator (URL).
1417 *
1418 * @param string $url The URL to be validated
1419 * @return boolean Whether the given URL is valid
1420 */
1421 public static function isValidUrl($url) {
1422 require_once(PATH_typo3 . 'contrib/idna/idna_convert.class.php');
1423 $IDN = new idna_convert(array('idn_version' => 2008));
1424
1425 return (filter_var($IDN->encode($url), FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED) !== FALSE);
1426 }
1427
1428 /*************************
1429 *
1430 * ARRAY FUNCTIONS
1431 *
1432 *************************/
1433
1434 /**
1435 * Check if an string item exists in an array.
1436 * Please note that the order of function parameters is reverse compared to the PHP function in_array()!!!
1437 *
1438 * Comparison to PHP in_array():
1439 * -> $array = array(0, 1, 2, 3);
1440 * -> variant_a := t3lib_div::inArray($array, $needle)
1441 * -> variant_b := in_array($needle, $array)
1442 * -> variant_c := in_array($needle, $array, TRUE)
1443 * +---------+-----------+-----------+-----------+
1444 * | $needle | variant_a | variant_b | variant_c |
1445 * +---------+-----------+-----------+-----------+
1446 * | '1a' | FALSE | TRUE | FALSE |
1447 * | '' | FALSE | TRUE | FALSE |
1448 * | '0' | TRUE | TRUE | FALSE |
1449 * | 0 | TRUE | TRUE | TRUE |
1450 * +---------+-----------+-----------+-----------+
1451 *
1452 * @param array $in_array One-dimensional array of items
1453 * @param string $item Item to check for
1454 * @return boolean TRUE if $item is in the one-dimensional array $in_array
1455 */
1456 public static function inArray(array $in_array, $item) {
1457 foreach ($in_array as $val) {
1458 if (!is_array($val) && !strcmp($val, $item)) {
1459 return TRUE;
1460 }
1461 }
1462 return FALSE;
1463 }
1464
1465 /**
1466 * Explodes a $string delimited by $delim and passes each item in the array through intval().
1467 * Corresponds to t3lib_div::trimExplode(), but with conversion to integers for all values.
1468 *
1469 * @param string $delimiter Delimiter string to explode with
1470 * @param string $string The string to explode
1471 * @param boolean $onlyNonEmptyValues If set, all empty values (='') will NOT be set in output
1472 * @param integer $limit If positive, the result will contain a maximum of limit elements,
1473 * if negative, all components except the last -limit are returned,
1474 * if zero (default), the result is not limited at all
1475 * @return array Exploded values, all converted to integers
1476 */
1477 public static function intExplode($delimiter, $string, $onlyNonEmptyValues = FALSE, $limit = 0) {
1478 $explodedValues = self::trimExplode($delimiter, $string, $onlyNonEmptyValues, $limit);
1479 return array_map('intval', $explodedValues);
1480 }
1481
1482 /**
1483 * Reverse explode which explodes the string counting from behind.
1484 * Thus t3lib_div::revExplode(':','my:words:here',2) will return array('my:words','here')
1485 *
1486 * @param string $delimiter Delimiter string to explode with
1487 * @param string $string The string to explode
1488 * @param integer $count Number of array entries
1489 * @return array Exploded values
1490 */
1491 public static function revExplode($delimiter, $string, $count = 0) {
1492 $explodedValues = explode($delimiter, strrev($string), $count);
1493 $explodedValues = array_map('strrev', $explodedValues);
1494 return array_reverse($explodedValues);
1495 }
1496
1497 /**
1498 * Explodes a string and trims all values for whitespace in the ends.
1499 * If $onlyNonEmptyValues is set, then all blank ('') values are removed.
1500 *
1501 * @param string $delim Delimiter string to explode with
1502 * @param string $string The string to explode
1503 * @param boolean $removeEmptyValues If set, all empty values will be removed in output
1504 * @param integer $limit If positive, the result will contain a maximum of
1505 * $limit elements, if negative, all components except
1506 * the last -$limit are returned, if zero (default),
1507 * the result is not limited at all. Attention though
1508 * that the use of this parameter can slow down this
1509 * function.
1510 * @return array Exploded values
1511 */
1512 public static function trimExplode($delim, $string, $removeEmptyValues = FALSE, $limit = 0) {
1513 $explodedValues = explode($delim, $string);
1514
1515 $result = array_map('trim', $explodedValues);
1516
1517 if ($removeEmptyValues) {
1518 $temp = array();
1519 foreach ($result as $value) {
1520 if ($value !== '') {
1521 $temp[] = $value;
1522 }
1523 }
1524 $result = $temp;
1525 }
1526
1527 if ($limit != 0) {
1528 if ($limit < 0) {
1529 $result = array_slice($result, 0, $limit);
1530 } elseif (count($result) > $limit) {
1531 $lastElements = array_slice($result, $limit - 1);
1532 $result = array_slice($result, 0, $limit - 1);
1533 $result[] = implode($delim, $lastElements);
1534 }
1535 }
1536
1537 return $result;
1538 }
1539
1540 /**
1541 * Removes the value $cmpValue from the $array if found there. Returns the modified array
1542 *
1543 * @param array $array Array containing the values
1544 * @param string $cmpValue Value to search for and if found remove array entry where found.
1545 * @return array Output array with entries removed if search string is found
1546 */
1547 public static function removeArrayEntryByValue(array $array, $cmpValue) {
1548 foreach ($array as $k => $v) {
1549 if (is_array($v)) {
1550 $array[$k] = self::removeArrayEntryByValue($v, $cmpValue);
1551 } elseif (!strcmp($v, $cmpValue)) {
1552 unset($array[$k]);
1553 }
1554 }
1555 return $array;
1556 }
1557
1558 /**
1559 * Filters an array to reduce its elements to match the condition.
1560 * The values in $keepItems can be optionally evaluated by a custom callback function.
1561 *
1562 * Example (arguments used to call this function):
1563 * $array = array(
1564 * array('aa' => array('first', 'second'),
1565 * array('bb' => array('third', 'fourth'),
1566 * array('cc' => array('fifth', 'sixth'),
1567 * );
1568 * $keepItems = array('third');
1569 * $getValueFunc = create_function('$value', 'return $value[0];');
1570 *
1571 * Returns:
1572 * array(
1573 * array('bb' => array('third', 'fourth'),
1574 * )
1575 *
1576 * @param array $array The initial array to be filtered/reduced
1577 * @param mixed $keepItems The items which are allowed/kept in the array - accepts array or csv string
1578 * @param string $getValueFunc (optional) Unique function name set by create_function() used to get the value to keep
1579 * @return array The filtered/reduced array with the kept items
1580 */
1581 public static function keepItemsInArray(array $array, $keepItems, $getValueFunc = NULL) {
1582 if ($array) {
1583 // Convert strings to arrays:
1584 if (is_string($keepItems)) {
1585 $keepItems = self::trimExplode(',', $keepItems);
1586 }
1587 // create_function() returns a string:
1588 if (!is_string($getValueFunc)) {
1589 $getValueFunc = NULL;
1590 }
1591 // Do the filtering:
1592 if (is_array($keepItems) && count($keepItems)) {
1593 foreach ($array as $key => $value) {
1594 // Get the value to compare by using the callback function:
1595 $keepValue = (isset($getValueFunc) ? $getValueFunc($value) : $value);
1596 if (!in_array($keepValue, $keepItems)) {
1597 unset($array[$key]);
1598 }
1599 }
1600 }
1601 }
1602 return $array;
1603 }
1604
1605 /**
1606 * Implodes a multidim-array into GET-parameters (eg. &param[key][key2]=value2&param[key][key3]=value3)
1607 *
1608 * @param string $name Name prefix for entries. Set to blank if you wish none.
1609 * @param array $theArray The (multidimensional) array to implode
1610 * @param string $str (keep blank)
1611 * @param boolean $skipBlank If set, parameters which were blank strings would be removed.
1612 * @param boolean $rawurlencodeParamName If set, the param name itself (for example "param[key][key2]") would be rawurlencoded as well.
1613 * @return string Imploded result, fx. &param[key][key2]=value2&param[key][key3]=value3
1614 * @see explodeUrl2Array()
1615 */
1616 public static function implodeArrayForUrl($name, array $theArray, $str = '', $skipBlank = FALSE, $rawurlencodeParamName = FALSE) {
1617 foreach ($theArray as $Akey => $AVal) {
1618 $thisKeyName = $name ? $name . '[' . $Akey . ']' : $Akey;
1619 if (is_array($AVal)) {
1620 $str = self::implodeArrayForUrl($thisKeyName, $AVal, $str, $skipBlank, $rawurlencodeParamName);
1621 } else {
1622 if (!$skipBlank || strcmp($AVal, '')) {
1623 $str .= '&' . ($rawurlencodeParamName ? rawurlencode($thisKeyName) : $thisKeyName) .
1624 '=' . rawurlencode($AVal);
1625 }
1626 }
1627 }
1628 return $str;
1629 }
1630
1631 /**
1632 * Explodes a string with GETvars (eg. "&id=1&type=2&ext[mykey]=3") into an array
1633 *
1634 * @param string $string GETvars string
1635 * @param boolean $multidim If set, the string will be parsed into a multidimensional array if square brackets are used in variable names (using PHP function parse_str())
1636 * @return array Array of values. All values AND keys are rawurldecoded() as they properly should be. But this means that any implosion of the array again must rawurlencode it!
1637 * @see implodeArrayForUrl()
1638 */
1639 public static function explodeUrl2Array($string, $multidim = FALSE) {
1640 $output = array();
1641 if ($multidim) {
1642 parse_str($string, $output);
1643 } else {
1644 $p = explode('&', $string);
1645 foreach ($p as $v) {
1646 if (strlen($v)) {
1647 list($pK, $pV) = explode('=', $v, 2);
1648 $output[rawurldecode($pK)] = rawurldecode($pV);
1649 }
1650 }
1651 }
1652 return $output;
1653 }
1654
1655 /**
1656 * Returns an array with selected keys from incoming data.
1657 * (Better read source code if you want to find out...)
1658 *
1659 * @param string $varList List of variable/key names
1660 * @param array $getArray Array from where to get values based on the keys in $varList
1661 * @param boolean $GPvarAlt If set, then t3lib_div::_GP() is used to fetch the value if not found (isset) in the $getArray
1662 * @return array Output array with selected variables.
1663 */
1664 public static function compileSelectedGetVarsFromArray($varList, array $getArray, $GPvarAlt = TRUE) {
1665 $keys = self::trimExplode(',', $varList, 1);
1666 $outArr = array();
1667 foreach ($keys as $v) {
1668 if (isset($getArray[$v])) {
1669 $outArr[$v] = $getArray[$v];
1670 } elseif ($GPvarAlt) {
1671 $outArr[$v] = self::_GP($v);
1672 }
1673 }
1674 return $outArr;
1675 }
1676
1677 /**
1678 * AddSlash array
1679 * This function traverses a multidimensional array and adds slashes to the values.
1680 * NOTE that the input array is and argument by reference.!!
1681 * Twin-function to stripSlashesOnArray
1682 *
1683 * @param array $theArray Multidimensional input array, (REFERENCE!)
1684 * @return array
1685 */
1686 public static function addSlashesOnArray(array &$theArray) {
1687 foreach ($theArray as &$value) {
1688 if (is_array($value)) {
1689 self::addSlashesOnArray($value);
1690 } else {
1691 $value = addslashes($value);
1692 }
1693 }
1694 unset($value);
1695 reset($theArray);
1696 }
1697
1698 /**
1699 * StripSlash array
1700 * This function traverses a multidimensional array and strips slashes to the values.
1701 * NOTE that the input array is and argument by reference.!!
1702 * Twin-function to addSlashesOnArray
1703 *
1704 * @param array $theArray Multidimensional input array, (REFERENCE!)
1705 * @return array
1706 */
1707 public static function stripSlashesOnArray(array &$theArray) {
1708 foreach ($theArray as &$value) {
1709 if (is_array($value)) {
1710 self::stripSlashesOnArray($value);
1711 } else {
1712 $value = stripslashes($value);
1713 }
1714 }
1715 unset($value);
1716 reset($theArray);
1717 }
1718
1719 /**
1720 * Either slashes ($cmd=add) or strips ($cmd=strip) array $arr depending on $cmd
1721 *
1722 * @param array $arr Multidimensional input array
1723 * @param string $cmd "add" or "strip", depending on usage you wish.
1724 * @return array
1725 */
1726 public static function slashArray(array $arr, $cmd) {
1727 if ($cmd == 'strip') {
1728 self::stripSlashesOnArray($arr);
1729 }
1730 if ($cmd == 'add') {
1731 self::addSlashesOnArray($arr);
1732 }
1733 return $arr;
1734 }
1735
1736 /**
1737 * Rename Array keys with a given mapping table
1738 *
1739 * @param array $array Array by reference which should be remapped
1740 * @param array $mappingTable Array with remap information, array/$oldKey => $newKey)
1741 */
1742 public static function remapArrayKeys(&$array, $mappingTable) {
1743 if (is_array($mappingTable)) {
1744 foreach ($mappingTable as $old => $new) {
1745 if ($new && isset($array[$old])) {
1746 $array[$new] = $array[$old];
1747 unset ($array[$old]);
1748 }
1749 }
1750 }
1751 }
1752
1753
1754 /**
1755 * Merges two arrays recursively and "binary safe" (integer keys are
1756 * overridden as well), overruling similar values in the first array
1757 * ($arr0) with the values of the second array ($arr1)
1758 * In case of identical keys, ie. keeping the values of the second.
1759 *
1760 * @param array $arr0 First array
1761 * @param array $arr1 Second array, overruling the first array
1762 * @param boolean $notAddKeys If set, keys that are NOT found in $arr0 (first array) will not be set. Thus only existing value can/will be overruled from second array.
1763 * @param boolean $includeEmptyValues If set, values from $arr1 will overrule if they are empty or zero. Default: TRUE
1764 * @param boolean $enableUnsetFeature If set, special values "__UNSET" can be used in the second array in order to unset array keys in the resulting array.
1765 * @return array Resulting array where $arr1 values has overruled $arr0 values
1766 */
1767 public static function array_merge_recursive_overrule(array $arr0, array $arr1, $notAddKeys = FALSE, $includeEmptyValues = TRUE, $enableUnsetFeature = TRUE) {
1768 foreach ($arr1 as $key => $val) {
1769 if (is_array($arr0[$key])) {
1770 if (is_array($arr1[$key])) {
1771 $arr0[$key] = self::array_merge_recursive_overrule(
1772 $arr0[$key],
1773 $arr1[$key],
1774 $notAddKeys,
1775 $includeEmptyValues,
1776 $enableUnsetFeature
1777 );
1778 }
1779 } elseif (!$notAddKeys || isset($arr0[$key])) {
1780 if ($enableUnsetFeature && $val === '__UNSET') {
1781 unset($arr0[$key]);
1782 } elseif ($includeEmptyValues || $val) {
1783 $arr0[$key] = $val;
1784 }
1785 }
1786 }
1787
1788 reset($arr0);
1789 return $arr0;
1790 }
1791
1792 /**
1793 * An array_merge function where the keys are NOT renumbered as they happen to be with the real php-array_merge function. It is "binary safe" in the sense that integer keys are overridden as well.
1794 *
1795 * @param array $arr1 First array
1796 * @param array $arr2 Second array
1797 * @return array Merged result.
1798 */
1799 public static function array_merge(array $arr1, array $arr2) {
1800 return $arr2 + $arr1;
1801 }
1802
1803 /**
1804 * Filters keys off from first array that also exist in second array. Comparison is done by keys.
1805 * This method is a recursive version of php array_diff_assoc()
1806 *
1807 * @param array $array1 Source array
1808 * @param array $array2 Reduce source array by this array
1809 * @return array Source array reduced by keys also present in second array
1810 */
1811 public static function arrayDiffAssocRecursive(array $array1, array $array2) {
1812 $differenceArray = array();
1813 foreach ($array1 as $key => $value) {
1814 if (!array_key_exists($key, $array2)) {
1815 $differenceArray[$key] = $value;
1816 } elseif (is_array($value)) {
1817 if (is_array($array2[$key])) {
1818 $differenceArray[$key] = self::arrayDiffAssocRecursive($value, $array2[$key]);
1819 }
1820 }
1821 }
1822
1823 return $differenceArray;
1824 }
1825
1826 /**
1827 * Takes a row and returns a CSV string of the values with $delim (default is ,) and $quote (default is ") as separator chars.
1828 *
1829 * @param array $row Input array of values
1830 * @param string $delim Delimited, default is comma
1831 * @param string $quote Quote-character to wrap around the values.
1832 * @return string A single line of CSV
1833 */
1834 public static function csvValues(array $row, $delim = ',', $quote = '"') {
1835 $out = array();
1836 foreach ($row as $value) {
1837 $out[] = str_replace($quote, $quote . $quote, $value);
1838 }
1839 $str = $quote . implode($quote . $delim . $quote, $out) . $quote;
1840 return $str;
1841 }
1842
1843 /**
1844 * Removes dots "." from end of a key identifier of TypoScript styled array.
1845 * array('key.' => array('property.' => 'value')) --> array('key' => array('property' => 'value'))
1846 *
1847 * @param array $ts TypoScript configuration array
1848 * @return array TypoScript configuration array without dots at the end of all keys
1849 */
1850 public static function removeDotsFromTS(array $ts) {
1851 $out = array();
1852 foreach ($ts as $key => $value) {
1853 if (is_array($value)) {
1854 $key = rtrim($key, '.');
1855 $out[$key] = self::removeDotsFromTS($value);
1856 } else {
1857 $out[$key] = $value;
1858 }
1859 }
1860 return $out;
1861 }
1862
1863 /**
1864 * Sorts an array by key recursive - uses natural sort order (aAbB-zZ)
1865 *
1866 * @param array $array array to be sorted recursively, passed by reference
1867 * @return boolean TRUE if param is an array
1868 */
1869 public static function naturalKeySortRecursive(&$array) {
1870 if (!is_array($array)) {
1871 return FALSE;
1872 }
1873 uksort($array, 'strnatcasecmp');
1874 foreach ($array as $key => $value) {
1875 self::naturalKeySortRecursive($array[$key]);
1876 }
1877 return TRUE;
1878 }
1879
1880 /*************************
1881 *
1882 * HTML/XML PROCESSING
1883 *
1884 *************************/
1885
1886 /**
1887 * Returns an array with all attributes of the input HTML tag as key/value pairs. Attributes are only lowercase a-z
1888 * $tag is either a whole tag (eg '<TAG OPTION ATTRIB=VALUE>') or the parameter list (ex ' OPTION ATTRIB=VALUE>')
1889 * If an attribute is empty, then the value for the key is empty. You can check if it existed with isset()
1890 *
1891 * @param string $tag HTML-tag string (or attributes only)
1892 * @return array Array with the attribute values.
1893 */
1894 public static function get_tag_attributes($tag) {
1895 $components = self::split_tag_attributes($tag);
1896 // Attribute name is stored here
1897 $name = '';
1898 $valuemode = FALSE;
1899 $attributes = array();
1900 foreach ($components as $key => $val) {
1901 // 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
1902 if ($val != '=') {
1903 if ($valuemode) {
1904 if ($name) {
1905 $attributes[$name] = $val;
1906 $name = '';
1907 }
1908 } else {
1909 if ($key = strtolower(preg_replace('/[^[:alnum:]_\:\-]/', '', $val))) {
1910 $attributes[$key] = '';
1911 $name = $key;
1912 }
1913 }
1914 $valuemode = FALSE;
1915 } else {
1916 $valuemode = TRUE;
1917 }
1918 }
1919 return $attributes;
1920 }
1921
1922 /**
1923 * Returns an array with the 'components' from an attribute list from an HTML tag. The result is normally analyzed by get_tag_attributes
1924 * Removes tag-name if found
1925 *
1926 * @param string $tag HTML-tag string (or attributes only)
1927 * @return array Array with the attribute values.
1928 */
1929 public static function split_tag_attributes($tag) {
1930 $tag_tmp = trim(preg_replace('/^<[^[:space:]]*/', '', trim($tag)));
1931 // Removes any > in the end of the string
1932 $tag_tmp = trim(rtrim($tag_tmp, '>'));
1933
1934 $value = array();
1935 // Compared with empty string instead , 030102
1936 while (strcmp($tag_tmp, '')) {
1937 $firstChar = substr($tag_tmp, 0, 1);
1938 if (!strcmp($firstChar, '"') || !strcmp($firstChar, "'")) {
1939 $reg = explode($firstChar, $tag_tmp, 3);
1940 $value[] = $reg[1];
1941 $tag_tmp = trim($reg[2]);
1942 } elseif (!strcmp($firstChar, '=')) {
1943 $value[] = '=';
1944 // Removes = chars.
1945 $tag_tmp = trim(substr($tag_tmp, 1));
1946 } else {
1947 // There are '' around the value. We look for the next ' ' or '>'
1948 $reg = preg_split('/[[:space:]=]/', $tag_tmp, 2);
1949 $value[] = trim($reg[0]);
1950 $tag_tmp = trim(substr($tag_tmp, strlen($reg[0]), 1) . $reg[1]);
1951 }
1952 }
1953 reset($value);
1954 return $value;
1955 }
1956
1957 /**
1958 * Implodes attributes in the array $arr for an attribute list in eg. and HTML tag (with quotes)
1959 *
1960 * @param array $arr Array with attribute key/value pairs, eg. "bgcolor"=>"red", "border"=>0
1961 * @param boolean $xhtmlSafe If set the resulting attribute list will have a) all attributes in lowercase (and duplicates weeded out, first entry taking precedence) and b) all values htmlspecialchar()'ed. It is recommended to use this switch!
1962 * @param boolean $dontOmitBlankAttribs If TRUE, don't check if values are blank. Default is to omit attributes with blank values.
1963 * @return string Imploded attributes, eg. 'bgcolor="red" border="0"'
1964 */
1965 public static function implodeAttributes(array $arr, $xhtmlSafe = FALSE, $dontOmitBlankAttribs = FALSE) {
1966 if ($xhtmlSafe) {
1967 $newArr = array();
1968 foreach ($arr as $p => $v) {
1969 if (!isset($newArr[strtolower($p)])) {
1970 $newArr[strtolower($p)] = htmlspecialchars($v);
1971 }
1972 }
1973 $arr = $newArr;
1974 }
1975 $list = array();
1976 foreach ($arr as $p => $v) {
1977 if (strcmp($v, '') || $dontOmitBlankAttribs) {
1978 $list[] = $p . '="' . $v . '"';
1979 }
1980 }
1981 return implode(' ', $list);
1982 }
1983
1984 /**
1985 * Wraps JavaScript code XHTML ready with <script>-tags
1986 * Automatic re-indenting of the JS code is done by using the first line as indent reference.
1987 * This is nice for indenting JS code with PHP code on the same level.
1988 *
1989 * @param string $string JavaScript code
1990 * @param boolean $linebreak Wrap script element in line breaks? Default is TRUE.
1991 * @return string The wrapped JS code, ready to put into a XHTML page
1992 */
1993 public static function wrapJS($string, $linebreak = TRUE) {
1994 if (trim($string)) {
1995 // <script wrapped in nl?
1996 $cr = $linebreak ? LF : '';
1997
1998 // remove nl from the beginning
1999 $string = preg_replace('/^\n+/', '', $string);
2000 // re-ident to one tab using the first line as reference
2001 $match = array();
2002 if (preg_match('/^(\t+)/', $string, $match)) {
2003 $string = str_replace($match[1], TAB, $string);
2004 }
2005 $string = $cr . '<script type="text/javascript">
2006 /*<![CDATA[*/
2007 ' . $string . '
2008 /*]]>*/
2009 </script>' . $cr;
2010 }
2011 return trim($string);
2012 }
2013
2014
2015 /**
2016 * Parses XML input into a PHP array with associative keys
2017 *
2018 * @param string $string XML data input
2019 * @param integer $depth Number of element levels to resolve the XML into an array. Any further structure will be set as XML.
2020 * @return mixed The array with the parsed structure unless the XML parser returns with an error in which case the error message string is returned.
2021 * @author bisqwit at iki dot fi dot not dot for dot ads dot invalid / http://dk.php.net/xml_parse_into_struct + kasperYYYY@typo3.com
2022 */
2023 public static function xml2tree($string, $depth = 999) {
2024 $parser = xml_parser_create();
2025 $vals = array();
2026 $index = array();
2027
2028 xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
2029 xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 0);
2030 xml_parse_into_struct($parser, $string, $vals, $index);
2031
2032 if (xml_get_error_code($parser)) {
2033 return 'Line ' . xml_get_current_line_number($parser) . ': ' . xml_error_string(xml_get_error_code($parser));
2034 }
2035 xml_parser_free($parser);
2036
2037 $stack = array(array());
2038 $stacktop = 0;
2039 $startPoint = 0;
2040
2041 $tagi = array();
2042 foreach ($vals as $key => $val) {
2043 $type = $val['type'];
2044
2045 // open tag:
2046 if ($type == 'open' || $type == 'complete') {
2047 $stack[$stacktop++] = $tagi;
2048
2049 if ($depth == $stacktop) {
2050 $startPoint = $key;
2051 }
2052
2053 $tagi = array('tag' => $val['tag']);
2054
2055 if (isset($val['attributes'])) {
2056 $tagi['attrs'] = $val['attributes'];
2057 }
2058 if (isset($val['value'])) {
2059 $tagi['values'][] = $val['value'];
2060 }
2061 }
2062 // finish tag:
2063 if ($type == 'complete' || $type == 'close') {
2064 $oldtagi = $tagi;
2065 $tagi = $stack[--$stacktop];
2066 $oldtag = $oldtagi['tag'];
2067 unset($oldtagi['tag']);
2068
2069 if ($depth == ($stacktop + 1)) {
2070 if ($key - $startPoint > 0) {
2071 $partArray = array_slice(
2072 $vals,
2073 $startPoint + 1,
2074 $key - $startPoint - 1
2075 );
2076 $oldtagi['XMLvalue'] = self::xmlRecompileFromStructValArray($partArray);
2077 } else {
2078 $oldtagi['XMLvalue'] = $oldtagi['values'][0];
2079 }
2080 }
2081
2082 $tagi['ch'][$oldtag][] = $oldtagi;
2083 unset($oldtagi);
2084 }
2085 // cdata
2086 if ($type == 'cdata') {
2087 $tagi['values'][] = $val['value'];
2088 }
2089 }
2090 return $tagi['ch'];
2091 }
2092
2093 /**
2094 * Turns PHP array into XML. See array2xml()
2095 *
2096 * @param array $array The input PHP array with any kind of data; text, binary, integers. Not objects though.
2097 * @param string $docTag Alternative document tag. Default is "phparray".
2098 * @param array $options Options for the compilation. See array2xml() for description.
2099 * @param string $charset Forced charset to prologue
2100 * @return string An XML string made from the input content in the array.
2101 * @see xml2array(),array2xml()
2102 */
2103 public static function array2xml_cs(array $array, $docTag = 'phparray', array $options = array(), $charset = '') {
2104
2105 // Set default charset unless explicitly specified
2106 $charset = $charset ? $charset : 'utf-8';
2107
2108 // Return XML:
2109 return '<?xml version="1.0" encoding="' . htmlspecialchars($charset) . '" standalone="yes" ?>' . LF .
2110 self::array2xml($array, '', 0, $docTag, 0, $options);
2111 }
2112
2113 /**
2114 * Deprecated to call directly (unless you are aware of using XML prologues)! Use "array2xml_cs" instead (which adds an XML-prologue)
2115 *
2116 * Converts a PHP array into an XML string.
2117 * The XML output is optimized for readability since associative keys are used as tag names.
2118 * This also means that only alphanumeric characters are allowed in the tag names AND only keys NOT starting with numbers (so watch your usage of keys!). However there are options you can set to avoid this problem.
2119 * Numeric keys are stored with the default tag name "numIndex" but can be overridden to other formats)
2120 * The function handles input values from the PHP array in a binary-safe way; All characters below 32 (except 9,10,13) will trigger the content to be converted to a base64-string
2121 * The PHP variable type of the data IS preserved as long as the types are strings, arrays, integers and booleans. Strings are the default type unless the "type" attribute is set.
2122 * The output XML has been tested with the PHP XML-parser and parses OK under all tested circumstances with 4.x versions. However, with PHP5 there seems to be the need to add an XML prologue a la <?xml version="1.0" encoding="[charset]" standalone="yes" ?> - otherwise UTF-8 is assumed! Unfortunately, many times the output from this function is used without adding that prologue meaning that non-ASCII characters will break the parsing!! This suchs of course! Effectively it means that the prologue should always be prepended setting the right characterset, alternatively the system should always run as utf-8!
2123 * However using MSIE to read the XML output didn't always go well: One reason could be that the character encoding is not observed in the PHP data. The other reason may be if the tag-names are invalid in the eyes of MSIE. Also using the namespace feature will make MSIE break parsing. There might be more reasons...
2124 *
2125 * @param array $array The input PHP array with any kind of data; text, binary, integers. Not objects though.
2126 * @param string $NSprefix tag-prefix, eg. a namespace prefix like "T3:"
2127 * @param integer $level Current recursion level. Don't change, stay at zero!
2128 * @param string $docTag Alternative document tag. Default is "phparray".
2129 * @param integer $spaceInd If greater than zero, then the number of spaces corresponding to this number is used for indenting, if less than zero - no indentation, if zero - a single TAB is used
2130 * @param array $options Options for the compilation. Key "useNindex" => 0/1 (boolean: whether to use "n0, n1, n2" for num. indexes); Key "useIndexTagForNum" => "[tag for numerical indexes]"; Key "useIndexTagForAssoc" => "[tag for associative indexes"; Key "parentTagMap" => array('parentTag' => 'thisLevelTag')
2131 * @param array $stackData Stack data. Don't touch.
2132 * @return string An XML string made from the input content in the array.
2133 * @see xml2array()
2134 */
2135 public static function array2xml(array $array, $NSprefix = '', $level = 0, $docTag = 'phparray', $spaceInd = 0, array $options = array(), array $stackData = array()) {
2136 // 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
2137 $binaryChars = chr(0) . chr(1) . chr(2) . chr(3) . chr(4) . chr(5) . chr(6) . chr(7) . chr(8) .
2138 chr(11) . chr(12) . chr(14) . chr(15) . chr(16) . chr(17) . chr(18) . chr(19) .
2139 chr(20) . chr(21) . chr(22) . chr(23) . chr(24) . chr(25) . chr(26) . chr(27) . chr(28) . chr(29) .
2140 chr(30) . chr(31);
2141 // Set indenting mode:
2142 $indentChar = $spaceInd ? ' ' : TAB;
2143 $indentN = $spaceInd > 0 ? $spaceInd : 1;
2144 $nl = ($spaceInd >= 0 ? LF : '');
2145
2146 // Init output variable:
2147 $output = '';
2148
2149 // Traverse the input array
2150 foreach ($array as $k => $v) {
2151 $attr = '';
2152 $tagName = $k;
2153
2154 // Construct the tag name.
2155 // Use tag based on grand-parent + parent tag name
2156 if (isset($options['grandParentTagMap'][$stackData['grandParentTagName'] . '/' . $stackData['parentTagName']])) {
2157 $attr .= ' index="' . htmlspecialchars($tagName) . '"';
2158 $tagName = (string) $options['grandParentTagMap'][$stackData['grandParentTagName'] . '/' . $stackData['parentTagName']];
2159 } elseif (isset($options['parentTagMap'][$stackData['parentTagName'] . ':_IS_NUM']) && t3lib_utility_Math::canBeInterpretedAsInteger($tagName)) { // Use tag based on parent tag name + if current tag is numeric
2160 $attr .= ' index="' . htmlspecialchars($tagName) . '"';
2161 $tagName = (string) $options['parentTagMap'][$stackData['parentTagName'] . ':_IS_NUM'];
2162 } elseif (isset($options['parentTagMap'][$stackData['parentTagName'] . ':' . $tagName])) { // Use tag based on parent tag name + current tag
2163 $attr .= ' index="' . htmlspecialchars($tagName) . '"';
2164 $tagName = (string) $options['parentTagMap'][$stackData['parentTagName'] . ':' . $tagName];
2165 } elseif (isset($options['parentTagMap'][$stackData['parentTagName']])) { // Use tag based on parent tag name:
2166 $attr .= ' index="' . htmlspecialchars($tagName) . '"';
2167 $tagName = (string) $options['parentTagMap'][$stackData['parentTagName']];
2168 } elseif (!strcmp(intval($tagName), $tagName)) { // If integer...;
2169 if ($options['useNindex']) { // If numeric key, prefix "n"
2170 $tagName = 'n' . $tagName;
2171 } else { // Use special tag for num. keys:
2172 $attr .= ' index="' . $tagName . '"';
2173 $tagName = $options['useIndexTagForNum'] ? $options['useIndexTagForNum'] : 'numIndex';
2174 }
2175 } elseif ($options['useIndexTagForAssoc']) { // Use tag for all associative keys:
2176 $attr .= ' index="' . htmlspecialchars($tagName) . '"';
2177 $tagName = $options['useIndexTagForAssoc'];
2178 }
2179
2180 // The tag name is cleaned up so only alphanumeric chars (plus - and _) are in there and not longer than 100 chars either.
2181 $tagName = substr(preg_replace('/[^[:alnum:]_-]/', '', $tagName), 0, 100);
2182
2183 // If the value is an array then we will call this function recursively:
2184 if (is_array($v)) {
2185
2186 // Sub elements:
2187 if ($options['alt_options'][$stackData['path'] . '/' . $tagName]) {
2188 $subOptions = $options['alt_options'][$stackData['path'] . '/' . $tagName];
2189 $clearStackPath = $subOptions['clearStackPath'];
2190 } else {
2191 $subOptions = $options;
2192 $clearStackPath = FALSE;
2193 }
2194
2195 $content = $nl .
2196 self::array2xml(
2197 $v,
2198 $NSprefix,
2199 $level + 1,
2200 '',
2201 $spaceInd,
2202 $subOptions,
2203 array(
2204 'parentTagName' => $tagName,
2205 'grandParentTagName' => $stackData['parentTagName'],
2206 'path' => $clearStackPath ? '' : $stackData['path'] . '/' . $tagName,
2207 )
2208 ) .
2209 ($spaceInd >= 0 ? str_pad('', ($level + 1) * $indentN, $indentChar) : '');
2210 // Do not set "type = array". Makes prettier XML but means that empty arrays are not restored with xml2array
2211 if ((int) $options['disableTypeAttrib'] != 2) {
2212 $attr .= ' type="array"';
2213 }
2214 } else { // Just a value:
2215
2216 // Look for binary chars:
2217 // Check for length, because PHP 5.2.0 may crash when first argument of strcspn is empty
2218 $vLen = strlen($v);
2219 // Go for base64 encoding if the initial segment NOT matching any binary char has the same length as the whole string!
2220 if ($vLen && strcspn($v, $binaryChars) != $vLen) {
2221 // If the value contained binary chars then we base64-encode it an set an attribute to notify this situation:
2222 $content = $nl . chunk_split(base64_encode($v));
2223 $attr .= ' base64="1"';
2224 } else {
2225 // Otherwise, just htmlspecialchar the stuff:
2226 $content = htmlspecialchars($v);
2227 $dType = gettype($v);
2228 if ($dType == 'string') {
2229 if ($options['useCDATA'] && $content != $v) {
2230 $content = '<![CDATA[' . $v . ']]>';
2231 }
2232 } elseif (!$options['disableTypeAttrib']) {
2233 $attr .= ' type="' . $dType . '"';
2234 }
2235 }
2236 }
2237
2238 // Add the element to the output string:
2239 $output .= ($spaceInd >= 0 ? str_pad('', ($level + 1) * $indentN, $indentChar) : '') . '<' . $NSprefix . $tagName . $attr . '>' . $content . '</' . $NSprefix . $tagName . '>' . $nl;
2240 }
2241
2242 // If we are at the outer-most level, then we finally wrap it all in the document tags and return that as the value:
2243 if (!$level) {
2244 $output =
2245 '<' . $docTag . '>' . $nl .
2246 $output .
2247 '</' . $docTag . '>';
2248 }
2249
2250 return $output;
2251 }
2252
2253 /**
2254 * Converts an XML string to a PHP array.
2255 * This is the reverse function of array2xml()
2256 * This is a wrapper for xml2arrayProcess that adds a two-level cache
2257 *
2258 * @param string $string XML content to convert into an array
2259 * @param string $NSprefix The tag-prefix resolve, eg. a namespace like "T3:"
2260 * @param boolean $reportDocTag If set, the document tag will be set in the key "_DOCUMENT_TAG" of the output array
2261 * @return mixed If the parsing had errors, a string with the error message is returned. Otherwise an array with the content.
2262 * @see array2xml(),xml2arrayProcess()
2263 */
2264 public static function xml2array($string, $NSprefix = '', $reportDocTag = FALSE) {
2265 static $firstLevelCache = array();
2266
2267 $identifier = md5($string . $NSprefix . ($reportDocTag ? '1' : '0'));
2268
2269 // Look up in first level cache
2270 if (!empty($firstLevelCache[$identifier])) {
2271 $array = $firstLevelCache[$identifier];
2272 } else {
2273 // Look up in second level cache
2274 $cacheContent = t3lib_pageSelect::getHash($identifier, 0);
2275 $array = unserialize($cacheContent);
2276
2277 if ($array === FALSE) {
2278 $array = self::xml2arrayProcess($string, $NSprefix, $reportDocTag);
2279 t3lib_pageSelect::storeHash($identifier, serialize($array), 'ident_xml2array');
2280 }
2281 // Store content in first level cache
2282 $firstLevelCache[$identifier] = $array;
2283 }
2284 return $array;
2285 }
2286
2287 /**
2288 * Converts an XML string to a PHP array.
2289 * This is the reverse function of array2xml()
2290 *
2291 * @param string $string XML content to convert into an array
2292 * @param string $NSprefix The tag-prefix resolve, eg. a namespace like "T3:"
2293 * @param boolean $reportDocTag If set, the document tag will be set in the key "_DOCUMENT_TAG" of the output array
2294 * @return mixed If the parsing had errors, a string with the error message is returned. Otherwise an array with the content.
2295 * @see array2xml()
2296 */
2297 protected static function xml2arrayProcess($string, $NSprefix = '', $reportDocTag = FALSE) {
2298 // Create parser:
2299 $parser = xml_parser_create();
2300 $vals = array();
2301 $index = array();
2302
2303 xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
2304 xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 0);
2305
2306 // Default output charset is UTF-8, only ASCII, ISO-8859-1 and UTF-8 are supported!!!
2307 $match = array();
2308 preg_match('/^[[:space:]]*<\?xml[^>]*encoding[[:space:]]*=[[:space:]]*"([^"]*)"/', substr($string, 0, 200), $match);
2309 $theCharset = $match[1] ? $match[1] : 'utf-8';
2310 // us-ascii / utf-8 / iso-8859-1
2311 xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $theCharset);
2312
2313 // Parse content:
2314 xml_parse_into_struct($parser, $string, $vals, $index);
2315
2316 // If error, return error message:
2317 if (xml_get_error_code($parser)) {
2318 return 'Line ' . xml_get_current_line_number($parser) . ': ' . xml_error_string(xml_get_error_code($parser));
2319 }
2320 xml_parser_free($parser);
2321
2322 // Init vars:
2323 $stack = array(array());
2324 $stacktop = 0;
2325 $current = array();
2326 $tagName = '';
2327 $documentTag = '';
2328
2329 // Traverse the parsed XML structure:
2330 foreach ($vals as $key => $val) {
2331
2332 // First, process the tag-name (which is used in both cases, whether "complete" or "close")
2333 $tagName = $val['tag'];
2334 if (!$documentTag) {
2335 $documentTag = $tagName;
2336 }
2337
2338 // Test for name space:
2339 $tagName = ($NSprefix && substr($tagName, 0, strlen($NSprefix)) == $NSprefix) ? substr($tagName, strlen($NSprefix)) : $tagName;
2340
2341 // Test for numeric tag, encoded on the form "nXXX":
2342 $testNtag = substr($tagName, 1); // Closing tag.
2343 $tagName = (substr($tagName, 0, 1) == 'n' && !strcmp(intval($testNtag), $testNtag)) ? intval($testNtag) : $tagName;
2344
2345 // Test for alternative index value:
2346 if (strlen($val['attributes']['index'])) {
2347 $tagName = $val['attributes']['index'];
2348 }
2349
2350 // Setting tag-values, manage stack:
2351 switch ($val['type']) {
2352 case 'open': // If open tag it means there is an array stored in sub-elements. Therefore increase the stackpointer and reset the accumulation array:
2353 // Setting blank place holder
2354 $current[$tagName] = array();
2355 $stack[$stacktop++] = $current;
2356 $current = array();
2357 break;
2358 case 'close': // If the tag is "close" then it is an array which is closing and we decrease the stack pointer.
2359 $oldCurrent = $current;
2360 $current = $stack[--$stacktop];
2361 // Going to the end of array to get placeholder key, key($current), and fill in array next:
2362 end($current);
2363 $current[key($current)] = $oldCurrent;
2364 unset($oldCurrent);
2365 break;
2366 case 'complete': // If "complete", then it's a value. If the attribute "base64" is set, then decode the value, otherwise just set it.
2367 if ($val['attributes']['base64']) {
2368 $current[$tagName] = base64_decode($val['value']);
2369 } else {
2370 // Had to cast it as a string - otherwise it would be evaluate FALSE if tested with isset()!!
2371 $current[$tagName] = (string) $val['value'];
2372
2373 // Cast type:
2374 switch ((string) $val['attributes']['type']) {
2375 case 'integer':
2376 $current[$tagName] = (integer) $current[$tagName];
2377 break;
2378 case 'double':
2379 $current[$tagName] = (double) $current[$tagName];
2380 break;
2381 case 'boolean':
2382 $current[$tagName] = (bool) $current[$tagName];
2383 break;
2384 case 'array':
2385 // 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...
2386 $current[$tagName] = array();
2387 break;
2388 }
2389 }
2390 break;
2391 }
2392 }
2393
2394 if ($reportDocTag) {
2395 $current[$tagName]['_DOCUMENT_TAG'] = $documentTag;
2396 }
2397
2398 // Finally return the content of the document tag.
2399 return $current[$tagName];
2400 }
2401
2402 /**
2403 * This implodes an array of XML parts (made with xml_parse_into_struct()) into XML again.
2404 *
2405 * @param array $vals An array of XML parts, see xml2tree
2406 * @return string Re-compiled XML data.
2407 */
2408 public static function xmlRecompileFromStructValArray(array $vals) {
2409 $XMLcontent = '';
2410
2411 foreach ($vals as $val) {
2412 $type = $val['type'];
2413
2414 // Open tag:
2415 if ($type == 'open' || $type == 'complete') {
2416 $XMLcontent .= '<' . $val['tag'];
2417 if (isset($val['attributes'])) {
2418 foreach ($val['attributes'] as $k => $v) {
2419 $XMLcontent .= ' ' . $k . '="' . htmlspecialchars($v) . '"';
2420 }
2421 }
2422 if ($type == 'complete') {
2423 if (isset($val['value'])) {
2424 $XMLcontent .= '>' . htmlspecialchars($val['value']) . '</' . $val['tag'] . '>';
2425 } else {
2426 $XMLcontent .= '/>';
2427 }
2428 } else {
2429 $XMLcontent .= '>';
2430 }
2431
2432 if ($type == 'open' && isset($val['value'])) {
2433 $XMLcontent .= htmlspecialchars($val['value']);
2434 }
2435 }
2436 // Finish tag:
2437 if ($type == 'close') {
2438 $XMLcontent .= '</' . $val['tag'] . '>';
2439 }
2440 // Cdata
2441 if ($type == 'cdata') {
2442 $XMLcontent .= htmlspecialchars($val['value']);
2443 }
2444 }
2445
2446 return $XMLcontent;
2447 }
2448
2449 /**
2450 * Extracts the attributes (typically encoding and version) of an XML prologue (header).
2451 *
2452 * @param string $xmlData XML data
2453 * @return array Attributes of the xml prologue (header)
2454 */
2455 public static function xmlGetHeaderAttribs($xmlData) {
2456 $match = array();
2457 if (preg_match('/^\s*<\?xml([^>]*)\?\>/', $xmlData, $match)) {
2458 return self::get_tag_attributes($match[1]);
2459 }
2460 }
2461
2462 /**
2463 * Minifies JavaScript
2464 *
2465 * @param string $script Script to minify
2466 * @param string $error Error message (if any)
2467 * @return string Minified script or source string if error happened
2468 */
2469 public static function minifyJavaScript($script, &$error = '') {
2470 require_once(PATH_typo3 . 'contrib/jsmin/jsmin.php');
2471 try {
2472 $error = '';
2473 $script = trim(JSMin::minify(str_replace(CR, '', $script)));
2474 }
2475 catch (JSMinException $e) {
2476 $error = 'Error while minifying JavaScript: ' . $e->getMessage();
2477 self::devLog($error, 't3lib_div', 2,
2478 array('JavaScript' => $script, 'Stack trace' => $e->getTrace()));
2479 }
2480 return $script;
2481 }
2482
2483 /*************************
2484 *
2485 * FILES FUNCTIONS
2486 *
2487 *************************/
2488
2489 /**
2490 * Reads the file or url $url and returns the content
2491 * If you are having trouble with proxys when reading URLs you can configure your way out of that with settings like $GLOBALS['TYPO3_CONF_VARS']['SYS']['curlUse'] etc.
2492 *
2493 * @param string $url File/URL to read
2494 * @param integer $includeHeader Whether the HTTP header should be fetched or not. 0=disable, 1=fetch header+content, 2=fetch header only
2495 * @param array $requestHeaders HTTP headers to be used in the request
2496 * @param array $report Error code/message and, if $includeHeader is 1, response meta data (HTTP status and content type)
2497 * @return mixed The content from the resource given as input. FALSE if an error has occured.
2498 */
2499 public static function getUrl($url, $includeHeader = 0, $requestHeaders = FALSE, &$report = NULL) {
2500 $content = FALSE;
2501
2502 if (isset($report)) {
2503 $report['error'] = 0;
2504 $report['message'] = '';
2505 }
2506
2507 // Use cURL for: http, https, ftp, ftps, sftp and scp
2508 if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['curlUse'] == '1' && preg_match('/^(?:http|ftp)s?|s(?:ftp|cp):/', $url)) {
2509 if (isset($report)) {
2510 $report['lib'] = 'cURL';
2511 }
2512
2513 // External URL without error checking.
2514 if (!function_exists('curl_init') || !($ch = curl_init())) {
2515 if (isset($report)) {
2516 $report['error'] = -1;
2517 $report['message'] = 'Couldn\'t initialize cURL.';
2518 }
2519 return FALSE;
2520 }
2521
2522 curl_setopt($ch, CURLOPT_URL, $url);
2523 curl_setopt($ch, CURLOPT_HEADER, $includeHeader ? 1 : 0);
2524 curl_setopt($ch, CURLOPT_NOBODY, $includeHeader == 2 ? 1 : 0);
2525 curl_setopt($ch, CURLOPT_HTTPGET, $includeHeader == 2 ? 'HEAD' : 'GET');
2526 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
2527 curl_setopt($ch, CURLOPT_FAILONERROR, 1);
2528 curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, max(0, intval($GLOBALS['TYPO3_CONF_VARS']['SYS']['curlTimeout'])));
2529
2530 $followLocation = @curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
2531
2532 if (is_array($requestHeaders)) {
2533 curl_setopt($ch, CURLOPT_HTTPHEADER, $requestHeaders);
2534 }
2535
2536 // (Proxy support implemented by Arco <arco@appeltaart.mine.nu>)
2537 if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['curlProxyServer']) {
2538 curl_setopt($ch, CURLOPT_PROXY, $GLOBALS['TYPO3_CONF_VARS']['SYS']['curlProxyServer']);
2539
2540 if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['curlProxyTunnel']) {
2541 curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, $GLOBALS['TYPO3_CONF_VARS']['SYS']['curlProxyTunnel']);
2542 }
2543 if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['curlProxyUserPass']) {
2544 curl_setopt($ch, CURLOPT_PROXYUSERPWD, $GLOBALS['TYPO3_CONF_VARS']['SYS']['curlProxyUserPass']);
2545 }
2546 }
2547 $content = curl_exec($ch);
2548 if (isset($report)) {
2549 if ($content === FALSE) {
2550 $report['error'] = curl_errno($ch);
2551 $report['message'] = curl_error($ch);
2552 } else {
2553 $curlInfo = curl_getinfo($ch);
2554 // We hit a redirection but we couldn't follow it
2555 if (!$followLocation && $curlInfo['status'] >= 300 && $curlInfo['status'] < 400) {
2556 $report['error'] = -1;
2557 $report['message'] = 'Couldn\'t follow location redirect (PHP configuration option open_basedir is in effect).';
2558 } elseif ($includeHeader) {
2559 // Set only for $includeHeader to work exactly like PHP variant
2560 $report['http_code'] = $curlInfo['http_code'];
2561 $report['content_type'] = $curlInfo['content_type'];
2562 }
2563 }
2564 }
2565 curl_close($ch);
2566
2567 } elseif ($includeHeader) {
2568 if (isset($report)) {
2569 $report['lib'] = 'socket';
2570 }
2571 $parsedURL = parse_url($url);
2572 if (!preg_match('/^https?/', $parsedURL['scheme'])) {
2573 if (isset($report)) {
2574 $report['error'] = -1;
2575 $report['message'] = 'Reading headers is not allowed for this protocol.';
2576 }
2577 return FALSE;
2578 }
2579 $port = intval($parsedURL['port']);
2580 if ($port < 1) {
2581 if ($parsedURL['scheme'] == 'http') {
2582 $port = ($port > 0 ? $port : 80);
2583 $scheme = '';
2584 } else {
2585 $port = ($port > 0 ? $port : 443);
2586 $scheme = 'ssl://';
2587 }
2588 }
2589 $errno = 0;
2590 $fp = @fsockopen($scheme . $parsedURL['host'], $port, $errno, $errstr, 2.0);
2591 if (!$fp || $errno > 0) {
2592 if (isset($report)) {
2593 $report['error'] = $errno ? $errno : -1;
2594 $report['message'] = $errno ? ($errstr ? $errstr : 'Socket error.') : 'Socket initialization error.';
2595 }
2596 return FALSE;
2597 }
2598 $method = ($includeHeader == 2) ? 'HEAD' : 'GET';
2599 $msg = $method . ' ' . (isset($parsedURL['path']) ? $parsedURL['path'] : '/') .
2600 ($parsedURL['query'] ? '?' . $parsedURL['query'] : '') .
2601 ' HTTP/1.0' . CRLF . 'Host: ' .
2602 $parsedURL['host'] . "\r\nConnection: close\r\n";
2603 if (is_array($requestHeaders)) {
2604 $msg .= implode(CRLF, $requestHeaders) . CRLF;
2605 }
2606 $msg .= CRLF;
2607
2608 fputs($fp, $msg);
2609 while (!feof($fp)) {
2610 $line = fgets($fp, 2048);
2611 if (isset($report)) {
2612 if (preg_match('|^HTTP/\d\.\d +(\d+)|', $line, $status)) {
2613 $report['http_code'] = $status[1];
2614 }
2615 elseif (preg_match('/^Content-Type: *(.*)/i', $line, $type)) {
2616 $report['content_type'] = $type[1];
2617 }
2618 }
2619 $content .= $line;
2620 if (!strlen(trim($line))) {
2621 // Stop at the first empty line (= end of header)
2622 break;
2623 }
2624 }
2625 if ($includeHeader != 2) {
2626 $content .= stream_get_contents($fp);
2627 }
2628 fclose($fp);
2629
2630 } elseif (is_array($requestHeaders)) {
2631 if (isset($report)) {
2632 $report['lib'] = 'file/context';
2633 }
2634 $parsedURL = parse_url($url);
2635 if (!preg_match('/^https?/', $parsedURL['scheme'])) {
2636 if (isset($report)) {
2637 $report['error'] = -1;
2638 $report['message'] = 'Sending request headers is not allowed for this protocol.';
2639 }
2640 return FALSE;
2641 }
2642 $ctx = stream_context_create(array(
2643 'http' => array(
2644 'header' => implode(CRLF, $requestHeaders)
2645 )
2646 )
2647 );
2648
2649 $content = @file_get_contents($url, FALSE, $ctx);
2650
2651 if ($content === FALSE && isset($report)) {
2652 $report['error'] = -1;
2653 $report['message'] = 'Couldn\'t get URL: ' . implode(LF, $http_response_header);
2654 }
2655 } else {
2656 if (isset($report)) {
2657 $report['lib'] = 'file';
2658 }
2659
2660 $content = @file_get_contents($url);
2661
2662 if ($content === FALSE && isset($report)) {
2663 $report['error'] = -1;
2664 $report['message'] = 'Couldn\'t get URL: ' . implode(LF, $http_response_header);
2665 }
2666 }
2667
2668 return $content;
2669 }
2670
2671 /**
2672 * Writes $content to the file $file
2673 *
2674 * @param string $file Filepath to write to
2675 * @param string $content Content to write
2676 * @return boolean TRUE if the file was successfully opened and written to.
2677 */
2678 public static function writeFile($file, $content) {
2679 if (!@is_file($file)) {
2680 $changePermissions = TRUE;
2681 }
2682
2683 if ($fd = fopen($file, 'wb')) {
2684 $res = fwrite($fd, $content);
2685 fclose($fd);
2686
2687 if ($res === FALSE) {
2688 return FALSE;
2689 }
2690
2691 // Change the permissions only if the file has just been created
2692 if ($changePermissions) {
2693 self::fixPermissions($file);
2694 }
2695
2696 return TRUE;
2697 }
2698
2699 return FALSE;
2700 }
2701
2702 /**
2703 * Sets the file system mode and group ownership of a file or a folder.
2704 *
2705 * @param string $path Path of file or folder, must not be escaped. Path can be absolute or relative
2706 * @param boolean $recursive If set, also fixes permissions of files and folders in the folder (if $path is a folder)
2707 * @return mixed TRUE on success, FALSE on error, always TRUE on Windows OS
2708 */
2709 public static function fixPermissions($path, $recursive = FALSE) {
2710 if (TYPO3_OS != 'WIN') {
2711 $result = FALSE;
2712
2713 // Make path absolute
2714 if (!self::isAbsPath($path)) {
2715 $path = self::getFileAbsFileName($path, FALSE);
2716 }
2717
2718 if (self::isAllowedAbsPath($path)) {
2719 if (@is_file($path)) {
2720 // "@" is there because file is not necessarily OWNED by the user
2721 $result = @chmod($path, octdec($GLOBALS['TYPO3_CONF_VARS']['BE']['fileCreateMask']));
2722 } elseif (@is_dir($path)) {
2723 // "@" is there because file is not necessarily OWNED by the user
2724 $result = @chmod($path, octdec($GLOBALS['TYPO3_CONF_VARS']['BE']['folderCreateMask']));
2725 }
2726
2727 // Set createGroup if not empty
2728 if ($GLOBALS['TYPO3_CONF_VARS']['BE']['createGroup']) {
2729 // "@" is there because file is not necessarily OWNED by the user
2730 $changeGroupResult = @chgrp($path, $GLOBALS['TYPO3_CONF_VARS']['BE']['createGroup']);
2731 $result = $changeGroupResult ? $result : FALSE;
2732 }
2733
2734 // Call recursive if recursive flag if set and $path is directory
2735 if ($recursive && @is_dir($path)) {
2736 $handle = opendir($path);
2737 while (($file = readdir($handle)) !== FALSE) {
2738 $recursionResult = NULL;
2739 if ($file !== '.' && $file !== '..') {
2740 if (@is_file($path . '/' . $file)) {
2741 $recursionResult = self::fixPermissions($path . '/' . $file);
2742 } elseif (@is_dir($path . '/' . $file)) {
2743 $recursionResult = self::fixPermissions($path . '/' . $file, TRUE);
2744 }
2745 if (isset($recursionResult) && !$recursionResult) {
2746 $result = FALSE;
2747 }
2748 }
2749 }
2750 closedir($handle);
2751 }
2752 }
2753 } else {
2754 $result = TRUE;
2755 }
2756 return $result;
2757 }
2758
2759 /**
2760 * Writes $content to a filename in the typo3temp/ folder (and possibly one or two subfolders...)
2761 * Accepts an additional subdirectory in the file path!
2762 *
2763 * @param string $filepath Absolute file path to write to inside "typo3temp/". First part of this string must match PATH_site."typo3temp/"
2764 * @param string $content Content string to write
2765 * @return string Returns NULL on success, otherwise an error string telling about the problem.
2766 */
2767 public static function writeFileToTypo3tempDir($filepath, $content) {
2768
2769 // Parse filepath into directory and basename:
2770 $fI = pathinfo($filepath);
2771 $fI['dirname'] .= '/';
2772
2773 // Check parts:
2774 if (self::validPathStr($filepath) && $fI['basename'] && strlen($fI['basename']) < 60) {
2775 if (defined('PATH_site')) {
2776 // Setting main temporary directory name (standard)
2777 $dirName = PATH_site . 'typo3temp/';
2778 if (@is_dir($dirName)) {
2779 if (self::isFirstPartOfStr($fI['dirname'], $dirName)) {
2780
2781 // Checking if the "subdir" is found:
2782 $subdir = substr($fI['dirname'], strlen($dirName));
2783 if ($subdir) {
2784 if (preg_match('/^[[:alnum:]_]+\/$/', $subdir) || preg_match('/^[[:alnum:]_]+\/[[:alnum:]_]+\/$/', $subdir)) {
2785 $dirName .= $subdir;
2786 if (!@is_dir($dirName)) {
2787 self::mkdir_deep(PATH_site . 'typo3temp/', $subdir);
2788 }
2789 } else {
2790 return 'Subdir, "' . $subdir . '", was NOT on the form "[[:alnum:]_]/" or "[[:alnum:]_]/[[:alnum:]_]/"';
2791 }
2792 }
2793 // Checking dir-name again (sub-dir might have been created):
2794 if (@is_dir($dirName)) {
2795 if ($filepath == $dirName . $fI['basename']) {
2796 self::writeFile($filepath, $content);
2797 if (!@is_file($filepath)) {
2798 return 'The file was not written to the disk. Please, check that you have write permissions to the typo3temp/ directory.';
2799 }
2800 } else {
2801 return 'Calculated filelocation didn\'t match input $filepath!';
2802 }
2803 } else {
2804 return '"' . $dirName . '" is not a directory!';
2805 }
2806 } else {
2807 return '"' . $fI['dirname'] . '" was not within directory PATH_site + "typo3temp/"';
2808 }
2809 } else {
2810 return 'PATH_site + "typo3temp/" was not a directory!';
2811 }
2812 } else {
2813 return 'PATH_site constant was NOT defined!';
2814 }
2815 } else {
2816 return 'Input filepath "' . $filepath . '" was generally invalid!';
2817 }
2818 }
2819
2820 /**
2821 * Wrapper function for mkdir.
2822 * Sets folder permissions according to $GLOBALS['TYPO3_CONF_VARS']['BE']['folderCreateMask']
2823 * and group ownership according to $GLOBALS['TYPO3_CONF_VARS']['BE']['createGroup']
2824 *
2825 * @param string $newFolder Absolute path to folder, see PHP mkdir() function. Removes trailing slash internally.
2826 * @return boolean TRUE if @mkdir went well!
2827 */
2828 public static function mkdir($newFolder) {
2829 $result = @mkdir($newFolder, octdec($GLOBALS['TYPO3_CONF_VARS']['BE']['folderCreateMask']));
2830 if ($result) {
2831 self::fixPermissions($newFolder);
2832 }
2833 return $result;
2834 }
2835
2836 /**
2837 * Creates a directory - including parent directories if necessary and
2838 * sets permissions on newly created directories.
2839 *
2840 * @param string $directory Target directory to create. Must a have trailing slash
2841 * if second parameter is given!
2842 * Example: "/root/typo3site/typo3temp/foo/"
2843 * @param string $deepDirectory Directory to create. This second parameter
2844 * is kept for backwards compatibility since 4.6 where this method
2845 * was split into a base directory and a deep directory to be created.
2846 * Example: "xx/yy/" which creates "/root/typo3site/xx/yy/" if $directory is "/root/typo3site/"
2847 * @return void
2848 * @throws \InvalidArgumentException If $directory or $deepDirectory are not strings
2849 * @throws \RuntimeException If directory could not be created
2850 */
2851 public static function mkdir_deep($directory, $deepDirectory = '') {
2852 if (!is_string($directory)) {
2853 throw new \InvalidArgumentException(
2854 'The specified directory is of type "' . gettype($directory) . '" but a string is expected.',
2855 1303662955
2856 );
2857 }
2858 if (!is_string($deepDirectory)) {
2859 throw new \InvalidArgumentException(
2860 'The specified directory is of type "' . gettype($deepDirectory) . '" but a string is expected.',
2861 1303662956
2862 );
2863 }
2864
2865 $fullPath = $directory . $deepDirectory;
2866 if (!is_dir($fullPath) && strlen($fullPath) > 0) {
2867 $firstCreatedPath = self::createDirectoryPath($fullPath);
2868 if ($firstCreatedPath !== '') {
2869 self::fixPermissions($firstCreatedPath, TRUE);
2870 }
2871 }
2872 }
2873
2874 /**
2875 * Creates directories for the specified paths if they do not exist. This
2876 * functions sets proper permission mask but does not set proper user and
2877 * group.
2878 *
2879 * @static
2880 * @param string $fullDirectoryPath
2881 * @return string Path to the the first created directory in the hierarchy
2882 * @see t3lib_div::mkdir_deep
2883 * @throws \RuntimeException If directory could not be created
2884 */
2885 protected static function createDirectoryPath($fullDirectoryPath) {
2886 $currentPath = $fullDirectoryPath;
2887 $firstCreatedPath = '';
2888 $permissionMask = octdec($GLOBALS['TYPO3_CONF_VARS']['BE']['folderCreateMask']);
2889 if (!@is_dir($currentPath)) {
2890 do {
2891 $firstCreatedPath = $currentPath;
2892 $separatorPosition = strrpos($currentPath, DIRECTORY_SEPARATOR);
2893 $currentPath = substr($currentPath, 0, $separatorPosition);
2894 } while (!is_dir($currentPath) && $separatorPosition !== FALSE);
2895
2896 $result = @mkdir($fullDirectoryPath, $permissionMask, TRUE);
2897 if (!$result) {
2898 throw new \RuntimeException('Could not create directory!', 1170251400);
2899 }
2900 }
2901 return $firstCreatedPath;
2902 }
2903
2904 /**
2905 * Wrapper function for rmdir, allowing recursive deletion of folders and files
2906 *
2907 * @param string $path Absolute path to folder, see PHP rmdir() function. Removes trailing slash internally.
2908 * @param boolean $removeNonEmpty Allow deletion of non-empty directories
2909 * @return boolean TRUE if @rmdir went well!
2910 */
2911 public static function rmdir($path, $removeNonEmpty = FALSE) {
2912 $OK = FALSE;
2913 // Remove trailing slash
2914 $path = preg_replace('|/$|', '', $path);
2915
2916 if (file_exists($path)) {
2917 $OK = TRUE;
2918
2919 if (is_dir($path)) {
2920 if ($removeNonEmpty == TRUE && $handle = opendir($path)) {
2921 while ($OK && FALSE !== ($file = readdir($handle))) {
2922 if ($file == '.' || $file == '..') {
2923 continue;
2924 }
2925 $OK = self::rmdir($path . '/' . $file, $removeNonEmpty);
2926 }
2927 closedir($handle);
2928 }
2929 if ($OK) {
2930 $OK = rmdir($path);
2931 }
2932
2933 } else { // If $dirname is a file, simply remove it
2934 $OK = unlink($path);
2935 }
2936
2937 clearstatcache();
2938 }
2939
2940 return $OK;
2941 }
2942
2943 /**
2944 * Returns an array with the names of folders in a specific path
2945 * Will return 'error' (string) if there were an error with reading directory content.
2946 *
2947 * @param string $path Path to list directories from
2948 * @return array Returns an array with the directory entries as values. If no path, the return value is nothing.
2949 */
2950 public static function get_dirs($path) {
2951 if ($path) {
2952 if (is_dir($path)) {
2953 $dir = scandir($path);
2954 $dirs = array();
2955 foreach ($dir as $entry) {
2956 if (is_dir($path . '/' . $entry) && $entry != '..' && $entry != '.') {
2957 $dirs[] = $entry;
2958 }
2959 }
2960 } else {
2961 $dirs = 'error';
2962 }
2963 }
2964 return $dirs;
2965 }
2966
2967 /**
2968 * Returns an array with the names of files in a specific path
2969 *
2970 * @param string $path Is the path to the file
2971 * @param string $extensionList is the comma list of extensions to read only (blank = all)
2972 * @param boolean $prependPath If set, then the path is prepended the file names. Otherwise only the file names are returned in the array
2973 * @param string $order is sorting: 1= sort alphabetically, 'mtime' = sort by modification time.
2974 *
2975 * @param string $excludePattern A comma separated list of file names to exclude, no wildcards
2976 * @return array Array of the files found
2977 */
2978 public static function getFilesInDir($path, $extensionList = '', $prependPath = FALSE, $order = '', $excludePattern = '') {
2979
2980 // Initialize variables:
2981 $filearray = array();
2982 $sortarray = array();
2983 $path = rtrim($path, '/');
2984
2985 // Find files+directories:
2986 if (@is_dir($path)) {
2987 $extensionList = strtolower($extensionList);
2988 $d = dir($path);
2989 if (is_object($d)) {
2990 while ($entry = $d->read()) {
2991 if (@is_file($path . '/' . $entry)) {
2992 $fI = pathinfo($entry);
2993 // Don't change this ever - extensions may depend on the fact that the hash is an md5 of the path! (import/export extension)
2994 $key = md5($path . '/' . $entry);
2995 if ((!strlen($extensionList) || self::inList($extensionList, strtolower($fI['extension']))) && (!strlen($excludePattern) || !preg_match('/^' . $excludePattern . '$/', $entry))) {
2996 $filearray[$key] = ($prependPath ? $path . '/' : '') . $entry;
2997 if ($order == 'mtime') {
2998 $sortarray[$key] = filemtime($path . '/' . $entry);
2999 }
3000 elseif ($order) {
3001 $sortarray[$key] = $entry;
3002 }
3003 }
3004 }
3005 }
3006 $d->close();
3007 } else {
3008 return 'error opening path: "' . $path . '"';
3009 }
3010 }
3011
3012 // Sort them:
3013 if ($order) {
3014 asort($sortarray);
3015 $newArr = array();
3016 foreach ($sortarray as $k => $v) {
3017 $newArr[$k] = $filearray[$k];
3018 }
3019 $filearray = $newArr;
3020 }
3021
3022 // Return result
3023 reset($filearray);
3024 return $filearray;
3025 }
3026
3027 /**
3028 * Recursively gather all files and folders of a path.
3029 *
3030 * @param array $fileArr Empty input array (will have files added to it)
3031 * @param string $path The path to read recursively from (absolute) (include trailing slash!)
3032 * @param string $extList Comma list of file extensions: Only files with extensions in this list (if applicable) will be selected.
3033 * @param boolean $regDirs If set, directories are also included in output.
3034 * @param integer $recursivityLevels The number of levels to dig down...
3035 * @param string $excludePattern regex pattern of files/directories to exclude
3036 * @return array An array with the found files/directories.
3037 */
3038 public static function getAllFilesAndFoldersInPath(array $fileArr, $path, $extList = '', $regDirs = FALSE, $recursivityLevels = 99, $excludePattern = '') {
3039 if ($regDirs) {
3040 $fileArr[] = $path;
3041 }
3042 $fileArr = array_merge($fileArr, self::getFilesInDir($path, $extList, 1, 1, $excludePattern));
3043
3044 $dirs = self::get_dirs($path);
3045 if (is_array($dirs) && $recursivityLevels > 0) {
3046 foreach ($dirs as $subdirs) {
3047 if ((string) $subdirs != '' && (!strlen($excludePattern) || !preg_match('/^' . $excludePattern . '$/', $subdirs))) {
3048 $fileArr = self::getAllFilesAndFoldersInPath($fileArr, $path . $subdirs . '/', $extList, $regDirs, $recursivityLevels - 1, $excludePattern);
3049 }
3050 }
3051 }
3052 return $fileArr;
3053 }
3054
3055 /**
3056 * Removes the absolute part of all files/folders in fileArr
3057 *
3058 * @param array $fileArr The file array to remove the prefix from
3059 * @param string $prefixToRemove The prefix path to remove (if found as first part of string!)
3060 * @return array The input $fileArr processed.
3061 */
3062 public static function removePrefixPathFromList(array $fileArr, $prefixToRemove) {
3063 foreach ($fileArr as $k => &$absFileRef) {
3064 if (self::isFirstPartOfStr($absFileRef, $prefixToRemove)) {
3065 $absFileRef = substr($absFileRef, strlen($prefixToRemove));
3066 } else {
3067 return 'ERROR: One or more of the files was NOT prefixed with the prefix-path!';
3068 }
3069 }
3070 unset($absFileRef);
3071 return $fileArr;
3072 }
3073
3074 /**
3075 * Fixes a path for windows-backslashes and reduces double-slashes to single slashes
3076 *
3077 * @param string $theFile File path to process
3078 * @return string
3079 */
3080 public static function fixWindowsFilePath($theFile) {
3081 return str_replace('//', '/', str_replace('\\', '/', $theFile));
3082 }
3083
3084 /**
3085 * Resolves "../" sections in the input path string.
3086 * For example "fileadmin/directory/../other_directory/" will be resolved to "fileadmin/other_directory/"
3087 *
3088 * @param string $pathStr File path in which "/../" is resolved
3089 * @return string
3090 */
3091 public static function resolveBackPath($pathStr) {
3092 $parts = explode('/', $pathStr);
3093 $output = array();
3094 $c = 0;
3095 foreach ($parts as $pV) {
3096 if ($pV == '..') {
3097 if ($c) {
3098 array_pop($output);
3099 $c--;
3100 } else {
3101 $output[] = $pV;
3102 }
3103 } else {
3104 $c++;
3105 $output[] = $pV;
3106 }
3107 }
3108 return implode('/', $output);
3109 }
3110
3111 /**
3112 * Prefixes a URL used with 'header-location' with 'http://...' depending on whether it has it already.
3113 * - If already having a scheme, nothing is prepended
3114 * - If having REQUEST_URI slash '/', then prefixing 'http://[host]' (relative to host)
3115 * - Otherwise prefixed with TYPO3_REQUEST_DIR (relative to current dir / TYPO3_REQUEST_DIR)
3116 *
3117 * @param string $path URL / path to prepend full URL addressing to.
3118 * @return string
3119 */
3120 public static function locationHeaderUrl($path) {
3121 $uI = parse_url($path);
3122 // relative to HOST
3123 if (substr($path, 0, 1) == '/') {
3124 $path = self::getIndpEnv('TYPO3_REQUEST_HOST') . $path;
3125 } elseif (!$uI['scheme']) { // No scheme either
3126 $path = self::getIndpEnv('TYPO3_REQUEST_DIR') . $path;
3127 }
3128 return $path;
3129 }
3130
3131 /**
3132 * Returns the maximum upload size for a file that is allowed. Measured in KB.
3133 * This might be handy to find out the real upload limit that is possible for this
3134 * TYPO3 installation. The first parameter can be used to set something that overrides
3135 * the maxFileSize, usually for the TCA values.
3136 *
3137 * @param integer $localLimit the number of Kilobytes (!) that should be used as
3138 * the initial Limit, otherwise $GLOBALS['TYPO3_CONF_VARS']['BE']['maxFileSize'] will be used
3139 * @return integer The maximum size of uploads that are allowed (measured in kilobytes)
3140 */
3141 public static function getMaxUploadFileSize($localLimit = 0) {
3142 // Don't allow more than the global max file size at all
3143 $t3Limit = (intval($localLimit > 0 ? $localLimit : $GLOBALS['TYPO3_CONF_VARS']['BE']['maxFileSize']));
3144 // As TYPO3 is handling the file size in KB, multiply by 1024 to get bytes
3145 $t3Limit = $t3Limit * 1024;
3146
3147 // Check for PHP restrictions of the maximum size of one of the $_FILES
3148 $phpUploadLimit = self::getBytesFromSizeMeasurement(ini_get('upload_max_filesize'));
3149 // Check for PHP restrictions of the maximum $_POST size
3150 $phpPostLimit = self::getBytesFromSizeMeasurement(ini_get('post_max_size'));
3151 // If the total amount of post data is smaller (!) than the upload_max_filesize directive,
3152 // then this is the real limit in PHP
3153 $phpUploadLimit = ($phpPostLimit < $phpUploadLimit ? $phpPostLimit : $phpUploadLimit);
3154
3155 // Is the allowed PHP limit (upload_max_filesize) lower than the TYPO3 limit?, also: revert back to KB
3156 return floor($phpUploadLimit < $t3Limit ? $phpUploadLimit : $t3Limit) / 1024;
3157 }
3158
3159 /**
3160 * Gets the bytes value from a measurement string like "100k".
3161 *
3162 * @param string $measurement The measurement (e.g. "100k")
3163 * @return integer The bytes value (e.g. 102400)
3164 */
3165 public static function getBytesFromSizeMeasurement($measurement) {
3166 $bytes = doubleval($measurement);
3167 if (stripos($measurement, 'G')) {
3168 $bytes *= 1024 * 1024 * 1024;
3169 } elseif (stripos($measurement, 'M')) {
3170 $bytes *= 1024 * 1024;
3171 } elseif (stripos($measurement, 'K')) {
3172 $bytes *= 1024;
3173 }
3174 return $bytes;
3175 }
3176
3177 /**
3178 * Retrieves the maximum path length that is valid in the current environment.
3179 *
3180 * @return integer The maximum available path length
3181 */
3182 public static function getMaximumPathLength() {
3183 return PHP_MAXPATHLEN;
3184 }
3185
3186 /**
3187 * Function for static version numbers on files, based on the filemtime
3188 *
3189 * This will make the filename automatically change when a file is
3190 * changed, and by that re-cached by the browser. If the file does not
3191 * exist physically the original file passed to the function is
3192 * returned without the timestamp.
3193 *
3194 * Behaviour is influenced by the setting
3195 * TYPO3_CONF_VARS[TYPO3_MODE][versionNumberInFilename]
3196 * = TRUE (BE) / "embed" (FE) : modify filename
3197 * = FALSE (BE) / "querystring" (FE) : add timestamp as parameter
3198 *
3199 * @param string $file Relative path to file including all potential query parameters (not htmlspecialchared yet)
3200 * @param boolean $forceQueryString If settings would suggest to embed in filename, this parameter allows us to force the versioning to occur in the query string. This is needed for scriptaculous.js which cannot have a different filename in order to load its modules (?load=...)
3201 * @return Relative path with version filename including the timestamp
3202 */
3203 public static function createVersionNumberedFilename($file, $forceQueryString = FALSE) {
3204 $lookupFile = explode('?', $file);
3205 $path = self::resolveBackPath(self::dirname(PATH_thisScript) . '/' . $lookupFile[0]);
3206
3207 if (TYPO3_MODE == 'FE') {
3208 $mode = strtolower($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['versionNumberInFilename']);
3209 if ($mode === 'embed') {
3210 $mode = TRUE;
3211 } else {
3212 if ($mode === 'querystring') {
3213 $mode = FALSE;
3214 } else {
3215 $doNothing = TRUE;
3216 }
3217 }
3218 } else {
3219 $mode = $GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['versionNumberInFilename'];
3220 }
3221
3222 if (!file_exists($path) || $doNothing) {
3223 // File not found, return filename unaltered
3224 $fullName = $file;
3225
3226 } else {
3227 if (!$mode || $forceQueryString) {
3228 // If use of .htaccess rule is not configured,
3229 // we use the default query-string method
3230 if ($lookupFile[1]) {
3231 $separator = '&';
3232 } else {
3233 $separator = '?';
3234 }
3235 $fullName = $file . $separator . filemtime($path);
3236
3237 } else {
3238 // Change the filename
3239 $name = explode('.', $lookupFile[0]);
3240 $extension = array_pop($name);
3241
3242 array_push($name, filemtime($path), $extension);
3243 $fullName = implode('.', $name);
3244 // Append potenti