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