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