79e2a422cd6e04e79c873cbfedb0c5b0c05fa5d9
[Packages/TYPO3.CMS.git] / typo3 / sysext / em / classes / tools / class.tx_em_tools.php
1 <?php
2 /* **************************************************************
3 * Copyright notice
4 *
5 * (c) webservices.nl
6 * (c) 2006-2010 Karsten Dambekalns <karsten@typo3.org>
7 * All rights reserved
8 *
9 * This script is part of the TYPO3 project. The TYPO3 project is
10 * free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * The GNU General Public License can be found at
16 * http://www.gnu.org/copyleft/gpl.html.
17 * A copy is found in the textfile GPL.txt and important notices to the license
18 * from the author is found in LICENSE.txt distributed with these scripts.
19 *
20 *
21 * This script is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 * GNU General Public License for more details.
25 *
26 * This copyright notice MUST APPEAR in all copies of the script!
27 ***************************************************************/
28 /**
29 * class.tx_em_tools.php
30 */
31
32 /**
33 * Static tools for extension manager
34 * Some of them should be moved later to t3lib static libraries
35 *
36 */
37 final class tx_em_Tools {
38
39 /**
40 * Keeps default categories.
41 *
42 * @var array
43 */
44 protected static $defaultCategories = array(
45 'be' => 0,
46 'module' => 1,
47 'fe' => 2,
48 'plugin' => 3,
49 'misc' => 4,
50 'services' => 5,
51 'templates' => 6,
52 'doc' => 8,
53 'example' => 9,
54 );
55 /**
56 * Keeps default states.
57 *
58 * @var array
59 */
60 protected static $defaultStates = array(
61 'alpha' => 0,
62 'beta' => 1,
63 'stable' => 2,
64 'experimental' => 3,
65 'test' => 4,
66 'obsolete' => 5,
67 'excludeFromUpdates' => 6,
68 'n/a' => 999,
69 );
70
71 /**
72 * Colors for states
73 *
74 * @var array
75 */
76 protected static $stateColors = array(
77 'alpha' => '#d12438',
78 'beta' => '#97b17e',
79 'stable' => '#3bb65c',
80 'experimental' => '#007eba',
81 'test' => '#979797',
82 'obsolete' => '#000000',
83 'excludeFromUpdates' => '#cf7307'
84 );
85
86 /**
87 * Gets the stateColor array
88 *
89 * @static
90 * @return array
91 */
92 public static function getStateColors() {
93 return self::$stateColors;
94 }
95
96 /**
97 * Unzips a zip file in the given path.
98 *
99 * Uses unzip binary if available, otherwise a pure PHP unzip is used.
100 *
101 * @param string $file Full path to zip file
102 * @param string $path Path to change to before extracting
103 * @return boolean TRUE on success, FALSE in failure
104 */
105 public static function unzip($file, $path) {
106 $unzipPath = trim($GLOBALS['TYPO3_CONF_VARS']['BE']['unzip_path']);
107 if (strlen($unzipPath)) {
108 if (substr($unzipPath, -1) !== '/' && is_dir($unzipPath)) {
109 // Make sure the path ends with a slash
110 $unzipPath.= '/';
111 }
112
113 chdir($path);
114 // for compatiblity reasons, we have to accept the full path of the unzip command
115 // or the directory containing the unzip binary
116 if (substr($unzipPath, -1) === '/') {
117 $cmd = $unzipPath . 'unzip -o ' . escapeshellarg($file);
118 } else {
119 $cmd = $unzipPath . ' -o ' . escapeshellarg($file);
120 }
121 t3lib_utility_Command::exec($cmd, $list, $ret);
122 return ($ret === 0);
123 } else {
124 // we use a pure PHP unzip
125 $unzip = t3lib_div::makeInstance('tx_em_Tools_Unzip', $file);
126 $ret = $unzip->extract(array('add_path' => $path));
127 return (is_array($ret));
128 }
129 }
130
131
132 /**
133 * Refreshes the global extension list
134 *
135 * @return void
136 */
137 public static function refreshGlobalExtList() {
138 Typo3_Bootstrap::getInstance()
139 ->populateTypo3LoadedExtGlobal(FALSE)
140 ->loadAdditionalConfigurationFromExtensions(FALSE);
141 }
142
143 /**
144 * Set category array entries for extension
145 *
146 * @param array Category index array
147 * @param array Part of list array for extension.
148 * @param string Extension key
149 * @return array Modified category index array
150 */
151 public static function setCat(&$cat, $listArrayPart, $extKey) {
152
153 // Getting extension title:
154 $extTitle = $listArrayPart['EM_CONF']['title'];
155
156 // Category index:
157 $index = $listArrayPart['EM_CONF']['category'];
158 $cat['cat'][$index][$extKey] = $extTitle;
159
160 // Author index:
161 $index = $listArrayPart['EM_CONF']['author'] . ($listArrayPart['EM_CONF']['author_company'] ? ', ' . $listArrayPart['EM_CONF']['author_company'] : '');
162 $cat['author_company'][$index][$extKey] = $extTitle;
163
164 // State index:
165 $index = $listArrayPart['EM_CONF']['state'];
166 $cat['state'][$index][$extKey] = $extTitle;
167
168 // Type index:
169 $index = $listArrayPart['type'];
170 $cat['type'][$index][$extKey] = $extTitle;
171
172 // Return categories:
173 return $cat;
174 }
175
176 /**
177 * Returns upload folder for extension
178 *
179 * @param string Extension key
180 * @return string Upload folder for extension
181 */
182 public static function uploadFolder($extKey) {
183 return 'uploads/tx_' . str_replace('_', '', $extKey) . '/';
184 }
185
186
187 /**
188 * Returns image tag for "uninstall"
189 *
190 * @return string <img> tag
191 */
192 public static function removeButton() {
193 return t3lib_iconWorks::getSpriteIcon('actions-system-extension-uninstall', array('title' => $GLOBALS['LANG']->getLL('ext_details_remove_ext')));
194 }
195
196 /**
197 * Returns image for "install"
198 *
199 * @return string <img> tag
200 */
201 public static function installButton() {
202 return t3lib_iconWorks::getSpriteIcon('actions-system-extension-install', array('title' => $GLOBALS['LANG']->getLL('helperFunction_install_extension')));
203 }
204
205 /**
206 * Warning (<img> + text string) message about the impossibility to import extensions (both local and global locations are disabled...)
207 *
208 * @return string <img> + text string.
209 */
210 public static function noImportMsg() {
211 return t3lib_iconWorks::getSpriteIcon('status-dialog-warning') .
212 '<strong>' . $GLOBALS['LANG']->getLL('helperFunction_import_not_possible') . '</strong>';
213 }
214
215
216 /**
217 * Fixes an old style ext_emconf.php array by adding constraints if needed and removing deprecated keys
218 *
219 * @param array $emConf
220 * @return array
221 */
222 public static function fixEMCONF($emConf) {
223 if (!isset($emConf['constraints']) || !isset($emConf['constraints']['depends']) || !isset($emConf['constraints']['conflicts']) || !isset($emConf['constraints']['suggests'])) {
224 if (!isset($emConf['constraints']) || !isset($emConf['constraints']['depends'])) {
225 $emConf['constraints']['depends'] = self::stringToDep($emConf['dependencies']);
226 if (strlen($emConf['PHP_version'])) {
227 $versionRange = self::splitVersionRange($emConf['PHP_version']);
228 if (version_compare($versionRange[0], '3.0.0', '<')) {
229 $versionRange[0] = '3.0.0';
230 }
231 if (version_compare($versionRange[1], '3.0.0', '<')) {
232 $versionRange[1] = '0.0.0';
233 }
234 $emConf['constraints']['depends']['php'] = implode('-', $versionRange);
235 }
236 if (strlen($emConf['TYPO3_version'])) {
237 $versionRange = self::splitVersionRange($emConf['TYPO3_version']);
238 if (version_compare($versionRange[0], '3.5.0', '<')) {
239 $versionRange[0] = '3.5.0';
240 }
241 if (version_compare($versionRange[1], '3.5.0', '<')) {
242 $versionRange[1] = '0.0.0';
243 }
244 $emConf['constraints']['depends']['typo3'] = implode('-', $versionRange);
245 }
246 }
247 if (!isset($emConf['constraints']) || !isset($emConf['constraints']['conflicts'])) {
248 $emConf['constraints']['conflicts'] = self::stringToDep($emConf['conflicts']);
249 }
250 if (!isset($emConf['constraints']) || !isset($emConf['constraints']['suggests'])) {
251 $emConf['constraints']['suggests'] = array();
252 }
253 } elseif (isset($emConf['constraints']) && isset($emConf['dependencies'])) {
254 $emConf['suggests'] = isset($emConf['suggests']) ? $emConf['suggests'] : array();
255 $emConf['dependencies'] = self::depToString($emConf['constraints']);
256 $emConf['conflicts'] = self::depToString($emConf['constraints'], 'conflicts');
257 }
258
259 // sanity check for version numbers, intentionally only checks php and typo3
260 if (isset($emConf['constraints']['depends']) && isset($emConf['constraints']['depends']['php'])) {
261 $versionRange = self::splitVersionRange($emConf['constraints']['depends']['php']);
262 if (version_compare($versionRange[0], '3.0.0', '<')) {
263 $versionRange[0] = '3.0.0';
264 }
265 if (version_compare($versionRange[1], '3.0.0', '<')) {
266 $versionRange[1] = '0.0.0';
267 }
268 $emConf['constraints']['depends']['php'] = implode('-', $versionRange);
269 }
270 if (isset($emConf['constraints']['depends']) && isset($emConf['constraints']['depends']['typo3'])) {
271 $versionRange = self::splitVersionRange($emConf['constraints']['depends']['typo3']);
272 if (version_compare($versionRange[0], '3.5.0', '<')) {
273 $versionRange[0] = '3.5.0';
274 }
275 if (version_compare($versionRange[1], '3.5.0', '<')) {
276 $versionRange[1] = '0.0.0';
277 }
278 $emConf['constraints']['depends']['typo3'] = implode('-', $versionRange);
279 }
280
281 unset($emConf['private']);
282 unset($emConf['download_password']);
283 unset($emConf['TYPO3_version']);
284 unset($emConf['PHP_version']);
285
286 return $emConf;
287 }
288
289
290 /**
291 * Returns the $EM_CONF array from an extensions ext_emconf.php file
292 *
293 * @param string Absolute path to EMCONF file.
294 * @param string Extension key.
295 * @return array EMconf array values.
296 */
297 public static function includeEMCONF($path, $_EXTKEY) {
298 $EM_CONF = NULL;
299 include($path);
300 if (is_array($EM_CONF[$_EXTKEY])) {
301 return self::fixEMCONF($EM_CONF[$_EXTKEY]);
302 }
303 return FALSE;
304 }
305
306
307 /**
308 * Extracts the directories in the $files array
309 *
310 * @param array Array of files / directories
311 * @return array Array of directories from the input array.
312 */
313 public static function extractDirsFromFileList($files) {
314 $dirs = array();
315
316 if (is_array($files)) {
317 // Traverse files / directories array:
318 foreach ($files as $file) {
319 if (substr($file, -1) == '/') {
320 $dirs[$file] = $file;
321 } else {
322 $pI = pathinfo($file);
323 if (strcmp($pI['dirname'], '') && strcmp($pI['dirname'], '.')) {
324 $dirs[$pI['dirname'] . '/'] = $pI['dirname'] . '/';
325 }
326 }
327 }
328 }
329 return $dirs;
330 }
331
332 /**
333 * Splits a version range into an array.
334 *
335 * If a single version number is given, it is considered a minimum value.
336 * If a dash is found, the numbers left and right are considered as minimum and maximum. Empty values are allowed.
337 *
338 * @param string $ver A string with a version range.
339 * @return array
340 */
341 public static function splitVersionRange($ver) {
342 $versionRange = array();
343 if (strstr($ver, '-')) {
344 $versionRange = explode('-', $ver, 2);
345 } else {
346 $versionRange[0] = $ver;
347 $versionRange[1] = '';
348 }
349
350 if (!$versionRange[0]) {
351 $versionRange[0] = '0.0.0';
352 }
353 if (!$versionRange[1]) {
354 $versionRange[1] = '0.0.0';
355 }
356
357 return $versionRange;
358 }
359
360 /**
361 * Checks whether the passed dependency is TER2-style (array) and returns a single string for displaying the dependencies.
362 *
363 * It leaves out all version numbers and the "php" and "typo3" dependencies, as they are implicit and of no interest without the version number.
364 *
365 * @param mixed $dep Either a string or an array listing dependencies.
366 * @param string $type The dependency type to list if $dep is an array
367 * @return string A simple dependency list for display
368 */
369 public static function depToString($dep, $type = 'depends') {
370 if (is_array($dep)) {
371 unset($dep[$type]['php']);
372 unset($dep[$type]['typo3']);
373 $s = (count($dep[$type])) ? implode(',', array_keys($dep[$type])) : '';
374 return $s;
375 }
376 return '';
377 }
378
379 /**
380 * Checks whether the passed dependency is TER-style (string) or TER2-style (array) and returns a single string for displaying the dependencies.
381 *
382 * It leaves out all version numbers and the "php" and "typo3" dependencies, as they are implicit and of no interest without the version number.
383 *
384 * @param mixed $dep Either a string or an array listing dependencies.
385 * @param string $type The dependency type to list if $dep is an array
386 * @return string A simple dependency list for display
387 */
388 public static function stringToDep($dep) {
389 $constraint = array();
390 if (is_string($dep) && strlen($dep)) {
391 $dep = explode(',', $dep);
392 foreach ($dep as $v) {
393 $constraint[$v] = '';
394 }
395 }
396 return $constraint;
397 }
398
399
400 /**
401 * Returns version information
402 *
403 * @param string Version code, x.x.x
404 * @param string part: "", "int", "main", "sub", "dev"
405 * @return string
406 * @see renderVersion()
407 */
408 public static function makeVersion($v, $mode) {
409 $vDat = self::renderVersion($v);
410 return $vDat['version_' . $mode];
411 }
412
413 /**
414 * Parses the version number x.x.x and returns an array with the various parts.
415 *
416 * @param string Version code, x.x.x
417 * @param string Increase version part: "main", "sub", "dev"
418 * @return string
419 */
420 public static function renderVersion($v, $raise = '') {
421 $parts = t3lib_div::intExplode('.', $v . '..');
422 $parts[0] = t3lib_utility_Math::forceIntegerInRange($parts[0], 0, 999);
423 $parts[1] = t3lib_utility_Math::forceIntegerInRange($parts[1], 0, 999);
424 $parts[2] = t3lib_utility_Math::forceIntegerInRange($parts[2], 0, 999);
425
426 switch ((string) $raise) {
427 case 'main':
428 $parts[0]++;
429 $parts[1] = 0;
430 $parts[2] = 0;
431 break;
432 case 'sub':
433 $parts[1]++;
434 $parts[2] = 0;
435 break;
436 case 'dev':
437 $parts[2]++;
438 break;
439 }
440
441 $res = array();
442 $res['version'] = $parts[0] . '.' . $parts[1] . '.' . $parts[2];
443 $res['version_int'] = intval($parts[0] * 1000000 + $parts[1] * 1000 + $parts[2]);
444 $res['version_main'] = $parts[0];
445 $res['version_sub'] = $parts[1];
446 $res['version_dev'] = $parts[2];
447
448 return $res;
449 }
450
451 /**
452 * Render version from intVersion
453 *
454 * @static
455 * @param int $intVersion
456 * @return string version
457 */
458 public static function versionFromInt($intVersion) {
459 $versionString = str_pad($intVersion, 9, '0', STR_PAD_LEFT);
460 $parts = array(
461 substr($versionString, 0, 3),
462 substr($versionString, 3, 3),
463 substr($versionString, 6, 3)
464 );
465 return intval($parts[0]) . '.' . intval($parts[1]) . '.' . intval($parts[2]);
466 }
467
468 /**
469 * Evaluates differences in version numbers with three parts, x.x.x. Returns TRUE if $v1 is greater than $v2
470 *
471 * @param string Version number 1
472 * @param string Version number 2
473 * @param integer Tolerance factor. For instance, set to 1000 to ignore difference in dev-version (third part)
474 * @return boolean TRUE if version 1 is greater than version 2
475 */
476 public static function versionDifference($v1, $v2, $div = 1) {
477 return floor(self::makeVersion($v1, 'int') / $div) > floor(self::makeVersion($v2, 'int') / $div);
478 }
479
480
481 /**
482 * Returns TRUE if the $str is found as the first part of a string in $array
483 *
484 * @param string String to test with.
485 * @param array Input array
486 * @param boolean If set, the test is case insensitive
487 * @return boolean TRUE if found.
488 */
489 public static function first_in_array($str, $array, $caseInsensitive = FALSE) {
490 if ($caseInsensitive) {
491 $str = strtolower($str);
492 }
493 if (is_array($array)) {
494 foreach ($array as $cl) {
495 if ($caseInsensitive) {
496 $cl = strtolower($cl);
497 }
498 if (t3lib_div::isFirstPartOfStr($cl, $str)) {
499 return TRUE;
500 }
501 }
502 }
503 return FALSE;
504 }
505
506 /**
507 * Compares two arrays with MD5-hash values for analysis of which files has changed.
508 *
509 * @param array Current values
510 * @param array Past values
511 * @return array Affected files
512 */
513 public static function findMD5ArrayDiff($current, $past) {
514 if (!is_array($current)) {
515 $current = array();
516 }
517 if (!is_array($past)) {
518 $past = array();
519 }
520 $filesInCommon = array_intersect($current, $past);
521 $diff1 = array_keys(array_diff($past, $filesInCommon));
522 $diff2 = array_keys(array_diff($current, $filesInCommon));
523 $affectedFiles = array_unique(array_merge($diff1, $diff2));
524 return $affectedFiles;
525 }
526
527 /**
528 * Returns title and style attribute for mouseover help text.
529 *
530 * @param string Help text.
531 * @return string title="" attribute prepended with a single space
532 */
533 public static function labelInfo($str) {
534 return ' title="' . htmlspecialchars($str) . '" style="cursor:help;"';
535 }
536
537
538 /**
539 * Returns the absolute path where the extension $extKey is installed (based on 'type' (SGL))
540 *
541 * @param string Extension key
542 * @param string Install scope type: L, G, S
543 * @return string Returns the absolute path to the install scope given by input $type variable. It is checked if the path is a directory. Slash is appended.
544 */
545 public static function getExtPath($extKey, $type, $returnWithoutExtKey = FALSE) {
546 $typePath = self::typePath($type);
547
548 if ($typePath) {
549 $path = $typePath . ($returnWithoutExtKey ? '' : $extKey . '/');
550 return $path;
551 } else {
552 return '';
553 }
554 }
555
556 /**
557 * Get type of extension (G,S,L) from extension path
558 *
559 * @param string $path
560 */
561 public static function getExtTypeFromPath($path) {
562 if (strpos($path, TYPO3_mainDir . 'sysext/') !== FALSE) {
563 return 'S';
564 } elseif (strpos($path, TYPO3_mainDir . 'ext/') !== FALSE) {
565 return 'G';
566 } elseif (strpos($path, 'typo3conf/ext/') !== FALSE) {
567 return 'L';
568 }
569 }
570
571 /**
572 * Get path from type
573 *
574 * @param string $type S/G/L
575 */
576 public static function typePath($type) {
577 if ($type === 'S') {
578 return PATH_typo3 . 'sysext/';
579 } elseif ($type === 'G') {
580 return PATH_typo3 . 'ext/';
581 } elseif ($type === 'L') {
582 return PATH_typo3conf . 'ext/';
583 }
584 }
585
586 /**
587 * Get relative path from type
588 *
589 * @param string $type S/G/L
590 */
591 public static function typeRelPath($type) {
592 if ($type === 'S') {
593 return 'sysext/';
594 } elseif ($type === 'G') {
595 return 'ext/';
596 } elseif ($type === 'L') {
597 return '../typo3conf/ext/';
598 }
599 }
600
601 /**
602 * Get backpath from type
603 *
604 * @param string $type S/G/L
605 */
606 public static function typeBackPath($type) {
607 if ($type === 'L') {
608 return '../../../../' . TYPO3_mainDir;
609 } else {
610 return '../../../';
611 }
612 }
613
614 /**
615 * Include a locallang file and return the $LOCAL_LANG array serialized.
616 *
617 * @param string Absolute path to locallang file to include.
618 * @param string Old content of a locallang file (keeping the header content)
619 * @return array Array with header/content as key 0/1
620 * @see makeUploadarray()
621 */
622 public static function getSerializedLocalLang($file, $content) {
623 $LOCAL_LANG = NULL;
624 $returnParts = explode('$LOCAL_LANG', $content, 2);
625
626 include($file);
627 if (is_array($LOCAL_LANG)) {
628 $returnParts[1] = serialize($LOCAL_LANG);
629 return $returnParts;
630 } else {
631 return array();
632 }
633 }
634
635
636 /**
637 * Enter description here...
638 *
639 * @param unknown_type $array
640 * @param unknown_type $lines
641 * @param unknown_type $level
642 * @return unknown
643 */
644 public static function arrayToCode($array, $level = 0) {
645 $lines = 'array(' . LF;
646 $level++;
647 foreach ($array as $k => $v) {
648 if (strlen($k) && is_array($v)) {
649 $lines .= str_repeat(TAB, $level) . "'" . $k . "' => " . self::arrayToCode($v, $level);
650 } elseif (strlen($k)) {
651 $lines .= str_repeat(TAB, $level) . "'" . $k . "' => " . (t3lib_utility_Math::canBeInterpretedAsInteger($v) ? intval($v) : "'" . t3lib_div::slashJS(trim($v), 1) . "'") . ',' . LF;
652 }
653 }
654
655 $lines .= str_repeat(TAB, $level - 1) . ')' . ($level - 1 == 0 ? '' : ',' . LF);
656 return $lines;
657 }
658
659
660 /**
661 * Traverse the array of installed extensions keys and arranges extensions in the priority order they should be in
662 *
663 * @param array Array of extension keys as values
664 * @param array Extension information array
665 * @return array Modified array of extention keys as values
666 * @see addExtToList()
667 */
668 public static function managesPriorities($listArr, $instExtInfo) {
669
670 // Initialize:
671 $levels = array(
672 'top' => array(),
673 'middle' => array(),
674 'bottom' => array(),
675 );
676
677 // Traverse list of extensions:
678 foreach ($listArr as $ext) {
679 $prio = trim($instExtInfo[$ext]['EM_CONF']['priority']);
680 switch ((string) $prio) {
681 case 'top':
682 case 'bottom':
683 $levels[$prio][] = $ext;
684 break;
685 default:
686 $levels['middle'][] = $ext;
687 break;
688 }
689 }
690 return array_merge(
691 $levels['top'],
692 $levels['middle'],
693 $levels['bottom']
694 );
695 }
696
697
698 /**
699 * Returns either array with all default categories or index/title
700 * of a category entry.
701 *
702 * @access public
703 * @param mixed $cat category title or category index
704 * @return mixed
705 */
706 public static function getDefaultCategory($cat = NULL) {
707 if (is_null($cat)) {
708 return self::$defaultCategories;
709 } else {
710 if (is_string($cat)) {
711 // default category
712 $catIndex = 4;
713 if (array_key_exists(strtolower($cat), self::$defaultCategories)) {
714 $catIndex = self::$defaultCategories[strtolower($cat)];
715 }
716 return $catIndex;
717 } else {
718 if (is_int($cat) && $cat >= 0) {
719 $catTitle = array_search($cat, self::$defaultCategories);
720 // default category
721 if (!$catTitle) {
722 $catTitle = 'misc';
723 }
724 return $catTitle;
725 }
726 }
727 }
728 }
729
730 /**
731 * Returns either array with all default states or index/title
732 * of a state entry.
733 *
734 * @access public
735 * @param mixed $state state title or state index
736 * @return mixed
737 */
738 public static function getDefaultState($state = NULL) {
739 if (is_null($state)) {
740 return self::$defaultStates;
741 } else {
742 if (is_string($state)) {
743 // default state
744 $stateIndex = 999;
745 if (array_key_exists(strtolower($state), self::$defaultStates)) {
746 $stateIndex = self::$defaultStates[strtolower($state)];
747 }
748 return $stateIndex;
749 } else {
750 if (is_int($state) && $state >= 0) {
751 $stateTitle = array_search($state, self::$defaultStates);
752 // default state
753 if (!$stateTitle) {
754 $stateTitle = 'n/a';
755 }
756 return $stateTitle;
757 }
758 }
759 }
760 }
761
762 /**
763 * Extension States
764 * Content must be redundant with the same internal variable as in class.tx_extrep.php!
765 *
766 * @static
767 * @return array
768 */
769 public static function getStates() {
770 return array(
771 'alpha' => $GLOBALS['LANG']->sL('LLL:EXT:em/language/locallang.xml:state_alpha'),
772 'beta' => $GLOBALS['LANG']->sL('LLL:EXT:em/language/locallang.xml:state_beta'),
773 'stable' => $GLOBALS['LANG']->sL('LLL:EXT:em/language/locallang.xml:state_stable'),
774 'experimental' => $GLOBALS['LANG']->sL('LLL:EXT:em/language/locallang.xml:state_experimental'),
775 'test' => $GLOBALS['LANG']->sL('LLL:EXT:em/language/locallang.xml:state_test'),
776 'obsolete' => $GLOBALS['LANG']->sL('LLL:EXT:em/language/locallang.xml:state_obsolete'),
777 'excludeFromUpdates' => $GLOBALS['LANG']->sL('LLL:EXT:em/language/locallang.xml:state_exclude_from_updates')
778 );
779 }
780
781 /**
782 * Reports back if installation in a certain scope is possible.
783 *
784 * @param string Scope: G, L, S
785 * @param string Extension lock-type (eg. "L" or "G")
786 * @return boolean TRUE if installation is allowed.
787 */
788 public static function importAsType($type, $lockType = '') {
789 switch ($type) {
790 case 'G':
791 return $GLOBALS['TYPO3_CONF_VARS']['EXT']['allowGlobalInstall'] && (!$lockType || !strcmp($lockType, $type));
792 break;
793 case 'L':
794 return $GLOBALS['TYPO3_CONF_VARS']['EXT']['allowLocalInstall'] && (!$lockType || !strcmp($lockType, $type));
795 break;
796 case 'S':
797 return isset($GLOBALS['TYPO3_CONF_VARS']['EXT']['allowSystemInstall']) && $GLOBALS['TYPO3_CONF_VARS']['EXT']['allowSystemInstall'];
798 break;
799 default:
800 return FALSE;
801 }
802 }
803
804 /**
805 * Returns TRUE if extensions in scope, $type, can be deleted (or installed for that sake)
806 *
807 * @param string Scope: "G" or "L"
808 * @return boolean TRUE if possible.
809 */
810 public static function deleteAsType($type) {
811 switch ($type) {
812 case 'G':
813 return $GLOBALS['TYPO3_CONF_VARS']['EXT']['allowGlobalInstall'];
814 break;
815 case 'L':
816 return $GLOBALS['TYPO3_CONF_VARS']['EXT']['allowLocalInstall'];
817 break;
818 default:
819 return FALSE;
820 }
821 }
822
823
824 /**
825 * Creates directories in $extDirPath
826 *
827 * @param array Array of directories to create relative to extDirPath, eg. "blabla", "blabla/blabla" etc...
828 * @param string Absolute path to directory.
829 * @return mixed Returns FALSE on success or an error string
830 */
831 public static function createDirsInPath($dirs, $extDirPath) {
832 if (is_array($dirs)) {
833 foreach ($dirs as $dir) {
834 $error = t3lib_div::mkdir_deep($extDirPath, $dir);
835 if ($error) {
836 return $error;
837 }
838 }
839 }
840
841 return FALSE;
842 }
843
844 /**
845 * Analyses the php-scripts of an available extension on server
846 *
847 * @param string Absolute path to extension
848 * @param string Prefix for tables/classes.
849 * @param string Extension key
850 * @return array Information array.
851 * @see makeDetailedExtensionAnalysis()
852 */
853 public static function getClassIndexLocallangFiles($absPath, $table_class_prefix, $extKey) {
854 $excludeForPackaging = $GLOBALS['TYPO3_CONF_VARS']['EXT']['excludeForPackaging'];
855 $filesInside = t3lib_div::removePrefixPathFromList(t3lib_div::getAllFilesAndFoldersInPath(array(), $absPath, 'php,inc', 0, 99, $excludeForPackaging), $absPath);
856 $out = array();
857 $reg = array();
858
859 foreach ($filesInside as $fileName) {
860 if (substr($fileName, 0, 4) != 'ext_' && substr($fileName, 0, 6) != 'tests/') { // ignore supposed-to-be unit tests as well
861 $baseName = basename($fileName);
862 if (substr($baseName, 0, 9) == 'locallang' && substr($baseName, -4) == '.php') {
863 $out['locallang'][] = $fileName;
864 } elseif ($baseName != 'conf.php') {
865 if (filesize($absPath . $fileName) < 500 * 1024) {
866 $fContent = t3lib_div::getUrl($absPath . $fileName);
867 unset($reg);
868 if (preg_match('/\n[[:space:]]*class[[:space:]]*([[:alnum:]_]+)([[:alnum:][:space:]_]*)/', $fContent, $reg)) {
869
870 // Find classes:
871 $lines = explode(LF, $fContent);
872 foreach ($lines as $l) {
873 $line = trim($l);
874 unset($reg);
875 if (preg_match('/^class[[:space:]]*([[:alnum:]_]+)([[:alnum:][:space:]_]*)/', $line, $reg)) {
876 $out['classes'][] = $reg[1];
877 $out['files'][$fileName]['classes'][] = $reg[1];
878 if ($reg[1] !== 'ext_update' && substr($reg[1], 0, 3) != 'ux_' && !t3lib_div::isFirstPartOfStr($reg[1], $table_class_prefix) && strcmp(substr($table_class_prefix, 0, -1), $reg[1])) {
879 $out['NSerrors']['classname'][] = $reg[1];
880 } else {
881 $out['NSok']['classname'][] = $reg[1];
882 }
883 }
884 }
885 // If class file prefixed 'class.'....
886 if (substr($baseName, 0, 6) == 'class.') {
887 $fI = pathinfo($baseName);
888 $testName = substr($baseName, 6, -(1 + strlen($fI['extension'])));
889 if ($testName !== 'ext_update' && substr($testName, 0, 3) != 'ux_' && !t3lib_div::isFirstPartOfStr($testName, $table_class_prefix) && strcmp(substr($table_class_prefix, 0, -1), $testName)) {
890 $out['NSerrors']['classfilename'][] = $baseName;
891 } else {
892 $out['NSok']['classfilename'][] = $baseName;
893 if (is_array($out['files'][$fileName]['classes']) && self::first_in_array($testName, $out['files'][$fileName]['classes'], 1)) {
894 $out['msg'][] = sprintf($GLOBALS['LANG']->getLL('detailedExtAnalysis_class_ok'),
895 $fileName, $testName
896 );
897 } else {
898 $out['errors'][] = sprintf($GLOBALS['LANG']->getLL('detailedExtAnalysis_class_not_ok'),
899 $fileName, $testName
900 );
901 }
902 }
903 }
904 // Check for proper XCLASS definition
905 // Match $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS'] with single or doublequotes
906 $XclassSearch = '\$TYPO3_CONF_VARS\[TYPO3_MODE\]\[[\'"]XCLASS[\'"]\]';
907 $XclassParts = preg_split('/if \(defined\([\'"]TYPO3_MODE[\'"]\)(.*)' . $XclassSearch . '/', $fContent, 2);
908 if (count($XclassParts) !== 2) {
909 // Match $GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS'] with single or doublequotes
910 $XclassSearch = '\$GLOBALS\[[\'"]TYPO3_CONF_VARS[\'"]\]\[TYPO3_MODE\]\[[\'"]XCLASS[\'"]\]';
911 $XclassParts = preg_split('/if \(defined\([\'"]TYPO3_MODE[\'"]\)(.*)' . $XclassSearch . '/', $fContent, 2);
912 }
913
914 if (count($XclassParts) == 2) {
915 unset($reg);
916 preg_match('/^\[[\'"]([[:alnum:]_\/\.]*)[\'"]\]/', $XclassParts[1], $reg);
917 if ($reg[1]) {
918 $cmpF = 'ext/' . $extKey . '/' . $fileName;
919 if (!strcmp($reg[1], $cmpF)) {
920 if (preg_match('/_once[[:space:]]*\(' . $XclassSearch . '\[[\'"]' . preg_quote($cmpF, '/') . '[\'"]\]\);/', $XclassParts[1])) {
921 $out['msg'][] = sprintf($GLOBALS['LANG']->getLL('detailedExtAnalysis_xclass_ok'), $fileName);
922 } else {
923 $out['errors'][] = $GLOBALS['LANG']->getLL('detailedExtAnalysis_xclass_no_include');
924 }
925 } else {
926 $out['errors'][] = sprintf($GLOBALS['LANG']->getLL('detailedExtAnalysis_xclass_incorrect'),
927 $reg[1], $cmpF
928 );
929 }
930 } else {
931 $out['errors'][] = sprintf($GLOBALS['LANG']->getLL('detailedExtAnalysis_no_xclass_filename'), $fileName);
932 }
933 } elseif (!self::first_in_array('ux_', $out['files'][$fileName]['classes'])) {
934 // No Xclass definition required if classname starts with 'ux_'
935 $out['errors'][] = sprintf($GLOBALS['LANG']->getLL('detailedExtAnalysis_no_xclass_found'), $fileName);
936 }
937 }
938 }
939 }
940 }
941 }
942 return $out;
943 }
944
945 /**
946 * Write new TYPO3_MOD_PATH to "conf.php" file.
947 *
948 * @param string Absolute path to a "conf.php" file of the backend module which we want to write back to.
949 * @param string Install scope type: L, G, S
950 * @param string Relative path for the module folder in extension
951 * @return string Returns message about the status.
952 * @see modConfFileAnalysis()
953 */
954 public static function writeTYPO3_MOD_PATH($confFilePath, $type, $mP) {
955 $lines = explode(LF, t3lib_div::getUrl($confFilePath));
956 $confFileInfo = array();
957 $confFileInfo['lines'] = $lines;
958 $reg = array();
959
960 $flag_M = 0;
961 $flag_B = 0;
962 $flag_Dispatch = 0;
963
964 foreach ($lines as $k => $l) {
965 $line = trim($l);
966
967 unset($reg);
968 if (preg_match('/^define[[:space:]]*\([[:space:]]*["\']TYPO3_MOD_PATH["\'][[:space:]]*,[[:space:]]*["\']([[:alnum:]_\/\.]+)["\'][[:space:]]*\)[[:space:]]*;/', $line, $reg)) {
969 $lines[$k] = str_replace($reg[0], 'define(\'TYPO3_MOD_PATH\', \'' . self::typeRelPath($type) . $mP . '\');', $lines[$k]);
970 $flag_M = $k + 1;
971 }
972
973 unset($reg);
974 if (preg_match('/^\$BACK_PATH[[:space:]]*=[[:space:]]*["\']([[:alnum:]_\/\.]+)["\'][[:space:]]*;/', $line, $reg)) {
975 $lines[$k] = str_replace($reg[0], '$BACK_PATH=\'' . self::typeBackPath($type) . '\';', $lines[$k]);
976 $flag_B = $k + 1;
977 }
978
979 // Check if this module uses new API (see http://bugs.typo3.org/view.php?id=5278)
980 // where TYPO3_MOD_PATH and BACK_PATH are not required
981 unset($reg);
982 if (preg_match('/^\$MCONF\[["\']script["\']\][[:space:]]*=[[:space:]]*["\']_DISPATCH["\'][[:space:]]*;/', $line, $reg)) {
983 $flag_Dispatch = $k + 1;
984 }
985
986 }
987
988 if ($flag_B && $flag_M) {
989 t3lib_div::writeFile($confFilePath, implode(LF, $lines));
990 return sprintf($GLOBALS['LANG']->getLL('writeModPath_ok'),
991 substr($confFilePath, strlen(PATH_site)));
992 } elseif ($flag_Dispatch) {
993 return sprintf(
994 $GLOBALS['LANG']->getLL('writeModPath_notRequired'),
995 substr($confFilePath, strlen(PATH_site))
996 );
997 } else {
998 return self::rfw(
999 sprintf($GLOBALS['LANG']->getLL('writeModPath_error'),
1000 $confFilePath)
1001 );
1002 }
1003 }
1004
1005 /**
1006 * Sends content of file for download
1007 *
1008 * @static
1009 * @param $path
1010 * @return void
1011 */
1012 public static function sendFile($path) {
1013 $path = t3lib_div::resolveBackPath(PATH_site . $path);
1014
1015 if (is_file($path) && is_readable($path) && t3lib_div::isAllowedAbsPath($path)) {
1016 header('Content-Type: application/octet-stream');
1017 header('Content-Disposition: attachment; filename=' . basename($path));
1018 readfile($path);
1019 exit;
1020 }
1021 }
1022
1023 /**
1024 * Rename a file / folder
1025 * @static
1026 * @param $file
1027 * @param $newName
1028 * @return bool
1029 */
1030 public static function renameFile($file, $newName) {
1031 if($file[0] == '/') {
1032 $file = substr($file, 1);
1033 }
1034 if($newName[0] == '/') {
1035 $newName = substr($newName, 1);
1036 }
1037
1038 $file = t3lib_div::resolveBackPath(PATH_site . $file);
1039 $newName = t3lib_div::resolveBackPath(PATH_site . $newName);
1040 if (is_writable($file) && t3lib_div::isAllowedAbsPath($file) && t3lib_div::isAllowedAbsPath($newName)) {
1041 return rename($file, $newName);
1042 }
1043
1044 return FALSE;
1045 }
1046
1047
1048 /**
1049 * Creates a new file
1050 *
1051 * Returns an array with
1052 * 0: boolean success
1053 * 1: string absolute path of written file/folder
1054 * 2: error code
1055 *
1056 * The error code returns
1057 * 0: no error
1058 * -1: not writable
1059 * -2: not allowed path
1060 * -3: already exists
1061 * -4: not able to create
1062 *
1063 * @static
1064 * @param $folder
1065 * @param $file
1066 * @param $isFolder
1067 * @return array
1068 */
1069 public static function createNewFile($folder, $file, $isFolder) {
1070 $success = FALSE;
1071 $error = 0;
1072
1073 if (substr($folder, -1) !== '/') {
1074 $folder .= '/';
1075 }
1076
1077 $newFile = t3lib_div::resolveBackPath(PATH_site . $folder . $file);
1078
1079 if (!is_writable(dirname($newFile))) {
1080 $error = -1;
1081 } elseif (!t3lib_div::isAllowedAbsPath($newFile)) {
1082 $error = -2;
1083 } elseif (file_exists($newFile)) {
1084 $error = -3;
1085 } else {
1086 if ($isFolder) {
1087 $success = t3lib_div::mkdir($newFile);
1088 } else {
1089 $success = t3lib_div::writeFile($newFile, '');
1090 }
1091
1092 if (!$success) {
1093 $error = -4;
1094 }
1095 }
1096
1097 return array(
1098 $success,
1099 $newFile,
1100 $error
1101 );
1102 }
1103
1104
1105 /**
1106 * Wrapping input string in a link tag with link to email address
1107 *
1108 * @param string Input string, being wrapped in <a> tags
1109 * @param string Email address for use in link.
1110 * @return string Output
1111 */
1112 public static function wrapEmail($str, $email) {
1113 if ($email) {
1114 $str = '<a href="mailto:' . htmlspecialchars($email) . '">' . htmlspecialchars($str) . '</a>';
1115 }
1116 return $str;
1117 }
1118
1119 /**
1120 * red-fontwrap. Returns the string wrapped in a <span>-tag defining the color to be red
1121 *
1122 * @param string Input string
1123 * @return string Output string
1124 */
1125 public static function rfw($string) {
1126 return '<span class="typo3-red">' . $string . '</span>';
1127 }
1128
1129 /**
1130 * dimmed-fontwrap. Returns the string wrapped in a <span>-tag defining the color to be gray/dimmed
1131 *
1132 * @param string Input string
1133 * @return string Output string
1134 */
1135 public static function dfw($string) {
1136 return '<span class="typo3-dimmed">' . $string . '</span>';
1137 }
1138 }
1139
1140 ?>