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