TYPO3 CMS  TYPO3_7-6
ResourceCompressor.php
Go to the documentation of this file.
1 <?php
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
21 
27 {
31  protected $targetDirectory = 'typo3temp/compressor/';
32 
36  protected $relativePath = '';
37 
41  protected $rootPath = '';
42 
46  protected $backPath = '';
47 
53  protected $createGzipped = false;
54 
58  protected $gzipCompressionLevel = -1;
59 
60  protected $htaccessTemplate = '<FilesMatch "\\.(js|css)(\\.gzip)?$">
61  <IfModule mod_expires.c>
62  ExpiresActive on
63  ExpiresDefault "access plus 7 days"
64  </IfModule>
65  FileETag MTime Size
66 </FilesMatch>';
67 
71  public function __construct()
72  {
73  // we check for existence of our targetDirectory
74  if (!is_dir(PATH_site . $this->targetDirectory)) {
75  GeneralUtility::mkdir(PATH_site . $this->targetDirectory);
76  }
77  // if enabled, we check whether we should auto-create the .htaccess file
78  if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['generateApacheHtaccess']) {
79  // check whether .htaccess exists
80  $htaccessPath = PATH_site . $this->targetDirectory . '.htaccess';
81  if (!file_exists($htaccessPath)) {
82  GeneralUtility::writeFile($htaccessPath, $this->htaccessTemplate);
83  }
84  }
85  // decide whether we should create gzipped versions or not
86  $compressionLevel = $GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['compressionLevel'];
87  // we need zlib for gzencode()
88  if (extension_loaded('zlib') && $compressionLevel) {
89  $this->createGzipped = true;
90  // $compressionLevel can also be TRUE
91  if (MathUtility::canBeInterpretedAsInteger($compressionLevel)) {
92  $this->gzipCompressionLevel = (int)$compressionLevel;
93  }
94  }
95  $this->setInitialPaths();
96  }
97 
103  public function setInitialPaths()
104  {
105  $this->setInitialRelativePath();
106  $this->setInitialRootPath();
107  $this->setInitialBackPath();
108  }
109 
115  protected function setInitialBackPath()
116  {
117  $backPath = TYPO3_MODE === 'BE' ? $GLOBALS['BACK_PATH'] : '';
118  $this->setBackPath($backPath);
119  }
120 
126  protected function setInitialRootPath()
127  {
128  $rootPath = TYPO3_MODE === 'BE' ? PATH_typo3 : PATH_site;
129  $this->setRootPath($rootPath);
130  }
131 
137  protected function setInitialRelativePath()
138  {
139  $relativePath = TYPO3_MODE === 'BE' ? $GLOBALS['BACK_PATH'] . '../' : '';
141  }
142 
150  {
151  if (is_string($relativePath)) {
152  $this->relativePath = $relativePath;
153  }
154  }
155 
162  public function setRootPath($rootPath)
163  {
164  if (is_string($rootPath)) {
165  $this->rootPath = $rootPath;
166  }
167  }
168 
175  public function setBackPath($backPath)
176  {
177  if (is_string($backPath)) {
178  $this->backPath = $backPath;
179  }
180  }
181 
192  public function concatenateCssFiles(array $cssFiles, array $options = [])
193  {
194  $filesToIncludeByType = ['all' => []];
195  foreach ($cssFiles as $key => $fileOptions) {
196  // no concatenation allowed for this file, so continue
197  if (!empty($fileOptions['excludeFromConcatenation'])) {
198  continue;
199  }
200  // we remove BACK_PATH from $filename, so make it relative to root path
201  $filenameFromMainDir = $this->getFilenameFromMainDir($fileOptions['file']);
202  // if $options['baseDirectories'] set, we only include files below these directories
203  if (
204  !isset($options['baseDirectories'])
205  || $this->checkBaseDirectory(
206  $filenameFromMainDir, array_merge($options['baseDirectories'], [$this->targetDirectory])
207  )
208  ) {
209  $type = isset($fileOptions['media']) ? strtolower($fileOptions['media']) : 'all';
210  if (!isset($filesToIncludeByType[$type])) {
211  $filesToIncludeByType[$type] = [];
212  }
213  if ($fileOptions['forceOnTop']) {
214  array_unshift($filesToIncludeByType[$type], $filenameFromMainDir);
215  } else {
216  $filesToIncludeByType[$type][] = $filenameFromMainDir;
217  }
218  // remove the file from the incoming file array
219  unset($cssFiles[$key]);
220  }
221  }
222  if (!empty($filesToIncludeByType)) {
223  foreach ($filesToIncludeByType as $mediaOption => $filesToInclude) {
224  if (empty($filesToInclude)) {
225  continue;
226  }
227  $targetFile = $this->createMergedCssFile($filesToInclude);
228  $targetFileRelative = $this->relativePath . $targetFile;
229  $concatenatedOptions = [
230  'file' => $targetFileRelative,
231  'rel' => 'stylesheet',
232  'media' => $mediaOption,
233  'compress' => true,
234  'excludeFromConcatenation' => true,
235  'forceOnTop' => false,
236  'allWrap' => ''
237  ];
238  // place the merged stylesheet on top of the stylesheets
239  $cssFiles = array_merge($cssFiles, [$targetFileRelative => $concatenatedOptions]);
240  }
241  }
242  return $cssFiles;
243  }
244 
251  public function concatenateJsFiles(array $jsFiles)
252  {
253  $filesToInclude = [];
254  foreach ($jsFiles as $key => $fileOptions) {
255  // invalid section found or no concatenation allowed, so continue
256  if (empty($fileOptions['section']) || !empty($fileOptions['excludeFromConcatenation'])) {
257  continue;
258  }
259  if (!isset($filesToInclude[$fileOptions['section']])) {
260  $filesToInclude[$fileOptions['section']] = [];
261  }
262  // we remove BACK_PATH from $filename, so make it relative to root path
263  $filenameFromMainDir = $this->getFilenameFromMainDir($fileOptions['file']);
264  if ($fileOptions['forceOnTop']) {
265  array_unshift($filesToInclude[$fileOptions['section']], $filenameFromMainDir);
266  } else {
267  $filesToInclude[$fileOptions['section']][] = $filenameFromMainDir;
268  }
269  // remove the file from the incoming file array
270  unset($jsFiles[$key]);
271  }
272  if (!empty($filesToInclude)) {
273  foreach ($filesToInclude as $section => $files) {
274  $targetFile = $this->createMergedJsFile($files);
275  $targetFileRelative = $this->relativePath . $targetFile;
276  $concatenatedOptions = [
277  'file' => $targetFileRelative,
278  'type' => 'text/javascript',
279  'section' => $section,
280  'compress' => true,
281  'excludeFromConcatenation' => true,
282  'forceOnTop' => false,
283  'allWrap' => ''
284  ];
285  // place the merged javascript on top of the JS files
286  $jsFiles = array_merge([$targetFileRelative => $concatenatedOptions], $jsFiles);
287  }
288  }
289  return $jsFiles;
290  }
291 
298  protected function createMergedCssFile(array $filesToInclude)
299  {
300  return $this->createMergedFile($filesToInclude, 'css');
301  }
302 
309  protected function createMergedJsFile(array $filesToInclude)
310  {
311  return $this->createMergedFile($filesToInclude, 'js');
312  }
313 
323  protected function createMergedFile(array $filesToInclude, $type = 'css')
324  {
325  // Get file type
326  $type = strtolower(trim($type, '. '));
327  if (empty($type)) {
328  throw new \InvalidArgumentException('No valid file type given for files to be merged.', 1308957498);
329  }
330  // we add up the filenames, filemtimes and filsizes to later build a checksum over
331  // it and include it in the temporary file name
332  $unique = '';
333  foreach ($filesToInclude as $key => $filename) {
334  if (GeneralUtility::isValidUrl($filename)) {
335  // check if it is possibly a local file with fully qualified URL
336  if (GeneralUtility::isOnCurrentHost($filename) &&
338  $filename,
339  GeneralUtility::getIndpEnv('TYPO3_SITE_URL')
340  )
341  ) {
342  // attempt to turn it into a local file path
343  $localFilename = substr($filename, strlen(GeneralUtility::getIndpEnv('TYPO3_SITE_URL')));
344  if (@is_file(GeneralUtility::resolveBackPath($this->rootPath . $localFilename))) {
345  $filesToInclude[$key] = $localFilename;
346  } else {
347  $filesToInclude[$key] = $this->retrieveExternalFile($filename);
348  }
349  } else {
350  $filesToInclude[$key] = $this->retrieveExternalFile($filename);
351  }
352  $filename = $filesToInclude[$key];
353  }
354  $filenameAbsolute = GeneralUtility::resolveBackPath($this->rootPath . $filename);
355  if (@file_exists($filenameAbsolute)) {
356  $fileStatus = stat($filenameAbsolute);
357  $unique .= $filenameAbsolute . $fileStatus['mtime'] . $fileStatus['size'];
358  } else {
359  $unique .= $filenameAbsolute;
360  }
361  }
362  $targetFile = $this->targetDirectory . 'merged-' . md5($unique) . '.' . $type;
363  // if the file doesn't already exist, we create it
364  if (!file_exists((PATH_site . $targetFile))) {
365  $concatenated = '';
366  // concatenate all the files together
367  foreach ($filesToInclude as $filename) {
368  $contents = GeneralUtility::getUrl(GeneralUtility::resolveBackPath($this->rootPath . $filename));
369  // remove any UTF-8 byte order mark (BOM) from files
370  if (StringUtility::beginsWith($contents, "\xEF\xBB\xBF")) {
371  $contents = substr($contents, 3);
372  }
373  // only fix paths if files aren't already in typo3temp (already processed)
374  if ($type === 'css' && !GeneralUtility::isFirstPartOfStr($filename, $this->targetDirectory)) {
375  $contents = $this->cssFixRelativeUrlPaths($contents, PathUtility::dirname($filename) . '/');
376  }
377  $concatenated .= LF . $contents;
378  }
379  // move @charset, @import and @namespace statements to top of new file
380  if ($type === 'css') {
381  $concatenated = $this->cssFixStatements($concatenated);
382  }
383  GeneralUtility::writeFile(PATH_site . $targetFile, $concatenated);
384  }
385  return $targetFile;
386  }
387 
394  public function compressCssFiles(array $cssFiles)
395  {
396  $filesAfterCompression = [];
397  foreach ($cssFiles as $key => $fileOptions) {
398  // if compression is enabled
399  if ($fileOptions['compress']) {
400  $filename = $this->compressCssFile($fileOptions['file']);
401  $fileOptions['compress'] = false;
402  $fileOptions['file'] = $filename;
403  $filesAfterCompression[$filename] = $fileOptions;
404  } else {
405  $filesAfterCompression[$key] = $fileOptions;
406  }
407  }
408  return $filesAfterCompression;
409  }
410 
423  public function compressCssFile($filename)
424  {
425  // generate the unique name of the file
426  $filenameAbsolute = GeneralUtility::resolveBackPath($this->rootPath . $this->getFilenameFromMainDir($filename));
427  if (@file_exists($filenameAbsolute)) {
428  $fileStatus = stat($filenameAbsolute);
429  $unique = $filenameAbsolute . $fileStatus['mtime'] . $fileStatus['size'];
430  } else {
431  $unique = $filenameAbsolute;
432  }
433 
434  $pathinfo = PathUtility::pathinfo($filename);
435  $targetFile = $this->targetDirectory . $pathinfo['filename'] . '-' . md5($unique) . '.css';
436  // only create it, if it doesn't exist, yet
437  if (!file_exists((PATH_site . $targetFile)) || $this->createGzipped && !file_exists((PATH_site . $targetFile . '.gzip'))) {
438  $contents = $this->compressCssString(GeneralUtility::getUrl($filenameAbsolute));
439  if (strpos($filename, $this->targetDirectory) === false) {
440  $contents = $this->cssFixRelativeUrlPaths($contents, PathUtility::dirname($filename) . '/');
441  }
442  $this->writeFileAndCompressed($targetFile, $contents);
443  }
444  return $this->relativePath . $this->returnFileReference($targetFile);
445  }
446 
455  public static function compressCssPregCallback($matches)
456  {
458  if ($matches[1]) {
459  // Group 1: Double quoted string.
460  return $matches[1];
461  } elseif ($matches[2]) {
462  // Group 2: Single quoted string.
463  return $matches[2];
464  } elseif ($matches[3]) {
465  // Group 3: Regular non-MacIE5-hack comment.
466  return '
467 ';
468  } elseif ($matches[4]) {
469  // Group 4: MacIE5-hack-type-1 comment.
470  return '
471 /*\\T1*/
472 ';
473  } elseif ($matches[5]) {
474  // Group 5,6,7: MacIE5-hack-type-2 comment
475  $matches[6] = preg_replace('/\\s++([+>{};,)])/S', '$1', $matches[6]);
476  // Clean pre-punctuation.
477  $matches[6] = preg_replace('/([+>{}:;,(])\\s++/S', '$1', $matches[6]);
478  // Clean post-punctuation.
479  $matches[6] = preg_replace('/;?\\}/S', '}
480 ', $matches[6]);
481  // Add a touch of formatting.
482  return '
483 /*T2\\*/' . $matches[6] . '
484 /*T2E*/
485 ';
486  } elseif ($matches[8]) {
487  // Group 8: calc function (see http://www.w3.org/TR/2006/WD-css3-values-20060919/#calc)
488  return 'calc' . $matches[8];
489  } elseif (isset($matches[9])) {
490  // Group 9: Non-string, non-comment. Safe to clean whitespace here.
491  $matches[9] = preg_replace('/^\\s++/', '', $matches[9]);
492  // Strip all leading whitespace.
493  $matches[9] = preg_replace('/\\s++$/', '', $matches[9]);
494  // Strip all trailing whitespace.
495  $matches[9] = preg_replace('/\\s{2,}+/', ' ', $matches[9]);
496  // Consolidate multiple whitespace.
497  $matches[9] = preg_replace('/\\s++([+>{};,)])/S', '$1', $matches[9]);
498  // Clean pre-punctuation.
499  $matches[9] = preg_replace('/([+>{}:;,(])\\s++/S', '$1', $matches[9]);
500  // Clean post-punctuation.
501  $matches[9] = preg_replace('/;?\\}/S', '}
502 ', $matches[9]);
503  // Add a touch of formatting.
504  return $matches[9];
505  }
506  return $matches[0] . '
507 /* ERROR! Unexpected _proccess_css_minify() parameter */
508 ';
509  }
510 
517  public function compressJsFiles(array $jsFiles)
518  {
519  $filesAfterCompression = [];
520  foreach ($jsFiles as $fileName => $fileOptions) {
521  // If compression is enabled
522  if ($fileOptions['compress']) {
523  $compressedFilename = $this->compressJsFile($fileOptions['file']);
524  $fileOptions['compress'] = false;
525  $fileOptions['file'] = $compressedFilename;
526  $filesAfterCompression[$compressedFilename] = $fileOptions;
527  } else {
528  $filesAfterCompression[$fileName] = $fileOptions;
529  }
530  }
531  return $filesAfterCompression;
532  }
533 
540  public function compressJsFile($filename)
541  {
542  // generate the unique name of the file
543  $filenameAbsolute = GeneralUtility::resolveBackPath($this->rootPath . $this->getFilenameFromMainDir($filename));
544  if (@file_exists($filenameAbsolute)) {
545  $fileStatus = stat($filenameAbsolute);
546  $unique = $filenameAbsolute . $fileStatus['mtime'] . $fileStatus['size'];
547  } else {
548  $unique = $filenameAbsolute;
549  }
550  $pathinfo = PathUtility::pathinfo($filename);
551  $targetFile = $this->targetDirectory . $pathinfo['filename'] . '-' . md5($unique) . '.js';
552  // only create it, if it doesn't exist, yet
553  if (!file_exists((PATH_site . $targetFile)) || $this->createGzipped && !file_exists((PATH_site . $targetFile . '.gzip'))) {
554  $contents = GeneralUtility::getUrl($filenameAbsolute);
555  $this->writeFileAndCompressed($targetFile, $contents);
556  }
557  return $this->relativePath . $this->returnFileReference($targetFile);
558  }
559 
566  protected function getFilenameFromMainDir($filename)
567  {
568  // if BACK_PATH is empty return $filename
569  if (empty($this->backPath)) {
570  return $filename;
571  }
572  // if the file exists in the root path, just return the $filename
573  if (strpos($filename, $this->backPath) === 0) {
574  $file = str_replace($this->backPath, '', $filename);
575  if (is_file(GeneralUtility::resolveBackPath($this->rootPath . $file))) {
576  return $file;
577  }
578  }
579  // if the file is from a special TYPO3 internal directory, add the missing typo3/ prefix
580  if (is_file(realpath(PATH_site . TYPO3_mainDir . $filename))) {
581  $filename = TYPO3_mainDir . $filename;
582  }
583  // build the file path relatively to the PATH_site
584  $backPath = str_replace(TYPO3_mainDir, '', $this->backPath);
585  $file = str_replace($backPath, '', $filename);
586  if (substr($file, 0, 3) === '../') {
587  $file = GeneralUtility::resolveBackPath(PATH_typo3 . $file);
588  } else {
589  $file = PATH_site . $file;
590  }
591  // check if the file exists, and if so, return the path relative to TYPO3_mainDir
592  if (is_file($file)) {
593  $mainDirDepth = substr_count(TYPO3_mainDir, '/');
594  return str_repeat('../', $mainDirDepth) . str_replace(PATH_site, '', $file);
595  }
596  // none of above conditions were met, fallback to default behaviour
597  return substr($filename, strlen($this->backPath));
598  }
599 
607  protected function checkBaseDirectory($filename, array $baseDirectories)
608  {
609  foreach ($baseDirectories as $baseDirectory) {
610  // check, if $filename starts with base directory
611  if (GeneralUtility::isFirstPartOfStr($filename, $baseDirectory)) {
612  return true;
613  }
614  }
615  return false;
616  }
617 
625  protected function cssFixRelativeUrlPaths($contents, $oldDir)
626  {
627  $mainDir = TYPO3_MODE === 'BE' ? TYPO3_mainDir : '';
628  $newDir = '../../' . $mainDir . $oldDir;
629  // Replace "url()" paths
630  if (stripos($contents, 'url') !== false) {
631  $regex = '/url(\\(\\s*["\']?(?!\\/)([^"\']+)["\']?\\s*\\))/iU';
632  $contents = $this->findAndReplaceUrlPathsByRegex($contents, $regex, $newDir, '(\'|\')');
633  }
634  // Replace "@import" paths
635  if (stripos($contents, '@import') !== false) {
636  $regex = '/@import\\s*(["\']?(?!\\/)([^"\']+)["\']?)/i';
637  $contents = $this->findAndReplaceUrlPathsByRegex($contents, $regex, $newDir, '"|"');
638  }
639  return $contents;
640  }
641 
651  protected function findAndReplaceUrlPathsByRegex($contents, $regex, $newDir, $wrap = '|')
652  {
653  $matches = [];
654  $replacements = [];
655  $wrap = explode('|', $wrap);
656  preg_match_all($regex, $contents, $matches);
657  foreach ($matches[2] as $matchCount => $match) {
658  // remove '," or white-spaces around
659  $match = trim($match, '\'" ');
660  // we must not rewrite paths containing ":" or "url(", e.g. data URIs (see RFC 2397)
661  if (strpos($match, ':') === false && !preg_match('/url\\s*\\(/i', $match)) {
662  $newPath = GeneralUtility::resolveBackPath($newDir . $match);
663  $replacements[$matches[1][$matchCount]] = $wrap[0] . $newPath . $wrap[1];
664  }
665  }
666  // replace URL paths in content
667  if (!empty($replacements)) {
668  $contents = str_replace(array_keys($replacements), array_values($replacements), $contents);
669  }
670  return $contents;
671  }
672 
680  protected function cssFixStatements($contents)
681  {
682  $matches = [];
683  $comment = LF . '/* moved by compressor */' . LF;
684  // nothing to do, so just return contents
685  if (stripos($contents, '@charset') === false && stripos($contents, '@import') === false && stripos($contents, '@namespace') === false) {
686  return $contents;
687  }
688  $regex = '/@(charset|import|namespace)\\s*(url)?\\s*\\(?\\s*["\']?[^"\'\\)]+["\']?\\s*\\)?\\s*;/i';
689  preg_match_all($regex, $contents, $matches);
690  if (!empty($matches[0])) {
691  // Ensure correct order of @charset, @namespace and @import
692  $charset = '';
693  $namespaces = [];
694  $imports = [];
695  foreach ($matches[1] as $index => $keyword) {
696  switch ($keyword) {
697  case 'charset':
698  if (empty($charset)) {
699  $charset = $matches[0][$index];
700  }
701  break;
702  case 'namespace':
703  $namespaces[] = $matches[0][$index];
704  break;
705  case 'import':
706  $imports[] = $matches[0][$index];
707  break;
708  }
709  }
710 
711  $namespaces = !empty($namespaces) ? implode('', $namespaces) . $comment : '';
712  $imports = !empty($imports) ? implode('', $imports) . $comment : '';
713  // remove existing statements
714  $contents = str_replace($matches[0], '', $contents);
715  // add statements to the top of contents in the order they occur in original file
716  $contents =
717  $charset
718  . $comment
719  . $namespaces
720  . $imports
721  . trim($contents);
722  }
723  return $contents;
724  }
725 
733  protected function writeFileAndCompressed($filename, $contents)
734  {
735  // write uncompressed file
736  GeneralUtility::writeFile(PATH_site . $filename, $contents);
737  if ($this->createGzipped) {
738  // create compressed version
739  GeneralUtility::writeFile(PATH_site . $filename . '.gzip', gzencode($contents, $this->gzipCompressionLevel));
740  }
741  }
742 
750  protected function returnFileReference($filename)
751  {
752  // if the client accepts gzip and we can create gzipped files, we give him compressed versions
753  if ($this->createGzipped && strpos(GeneralUtility::getIndpEnv('HTTP_ACCEPT_ENCODING'), 'gzip') !== false) {
754  return $filename . '.gzip';
755  } else {
756  return $filename;
757  }
758  }
759 
766  protected function retrieveExternalFile($url)
767  {
768  $externalContent = GeneralUtility::getUrl($url);
769  $filename = $this->targetDirectory . 'external-' . md5($url);
770  // Write only if file does not exist OR md5 of the content is not the same as fetched one
771  if (!file_exists(PATH_site . $filename)
772  || (md5($externalContent) !== md5(GeneralUtility::getUrl(PATH_site . $filename)))
773  ) {
774  GeneralUtility::writeFile(PATH_site . $filename, $externalContent);
775  }
776  return $filename;
777  }
778 
785  protected function compressCssString($contents)
786  {
787  // Perform some safe CSS optimizations.
788  // Regexp to match comment blocks.
789  $comment = '/\*[^*]*\*+(?:[^/*][^*]*\*+)*/';
790  // Regexp to match double quoted strings.
791  $double_quot = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"';
792  // Regexp to match single quoted strings.
793  $single_quot = "'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'";
794  // Strip all comment blocks, but keep double/single quoted strings.
795  $contents = preg_replace(
796  "<($double_quot|$single_quot)|$comment>Ss",
797  '$1',
798  $contents
799  );
800  // Remove certain whitespace.
801  // There are different conditions for removing leading and trailing
802  // whitespace.
803  // @see http://php.net/manual/regexp.reference.subpatterns.php
804  $contents = preg_replace('<
805  # Strip leading and trailing whitespace.
806  \s*([@{};,])\s*
807  # Strip only leading whitespace from:
808  # - Closing parenthesis: Retain "@media (bar) and foo".
809  | \s+([\)])
810  # Strip only trailing whitespace from:
811  # - Opening parenthesis: Retain "@media (bar) and foo".
812  # - Colon: Retain :pseudo-selectors.
813  | ([\(:])\s+
814  >xS',
815  // Only one of the three capturing groups will match, so its reference
816  // will contain the wanted value and the references for the
817  // two non-matching groups will be replaced with empty strings.
818  '$1$2$3',
819  $contents
820  );
821  // End the file with a new line.
822  $contents = trim($contents);
823  // Ensure file ends in newline.
824  $contents .= LF;
825  return $contents;
826  }
827 }
concatenateCssFiles(array $cssFiles, array $options=[])
static isFirstPartOfStr($str, $partStr)
findAndReplaceUrlPathsByRegex($contents, $regex, $newDir, $wrap='|')
static pathinfo($path, $options=null)
checkBaseDirectory($filename, array $baseDirectories)
createMergedFile(array $filesToInclude, $type='css')
static getUrl($url, $includeHeader=0, $requestHeaders=false, &$report=null)
static beginsWith($haystack, $needle)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
static writeFile($file, $content, $changePermissions=false)