[BUGFIX] Fix the unit tests to work with PHPUnit 3.6
[Packages/TYPO3.CMS.git] / t3lib / class.t3lib_compressor.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2010-2011 Steffen Gebert <steffen@steffen-gebert.de>
6 * (c) 2011 Kai Vogel <kai.vogel@speedprogs.de>
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 /**
30 * Compressor
31 * This merges and compresses CSS and JavaScript files of the TYPO3 Backend.
32 *
33 * @author Steffen Gebert <steffen@steffen-gebert.de>
34 * @package TYPO3
35 * @subpackage t3lib
36 */
37 class t3lib_Compressor {
38
39 protected $targetDirectory = 'typo3temp/compressor/';
40
41 protected $relativePath = '';
42
43 protected $rootPath = '';
44
45 protected $backPath = '';
46
47 // gzipped versions are only created if $TYPO3_CONF_VARS[TYPO3_MODE]['compressionLevel'] is set
48 protected $createGzipped = FALSE;
49 // default compression level is -1
50 protected $gzipCompressionLevel = -1;
51
52 protected $htaccessTemplate = '<FilesMatch "\.(js|css)(\.gzip)?$">
53 <IfModule mod_expires.c>
54 ExpiresActive on
55 ExpiresDefault "access plus 7 days"
56 </IfModule>
57 FileETag MTime Size
58 </FilesMatch>';
59
60 /**
61 * Constructor
62 */
63 public function __construct() {
64 // we check for existance of our targetDirectory
65 if (!is_dir(PATH_site . $this->targetDirectory)) {
66 t3lib_div::mkdir(PATH_site . $this->targetDirectory);
67 }
68
69 // if enabled, we check whether we should auto-create the .htaccess file
70 if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['generateApacheHtaccess']) {
71 // check whether .htaccess exists
72 $htaccessPath = PATH_site . $this->targetDirectory . '.htaccess';
73 if (!file_exists($htaccessPath)) {
74 t3lib_div::writeFile($htaccessPath, $this->htaccessTemplate);
75 }
76 }
77
78 // decide whether we should create gzipped versions or not
79 $compressionLevel = $GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['compressionLevel'];
80 // we need zlib for gzencode()
81 if (extension_loaded('zlib') && $compressionLevel) {
82 $this->createGzipped = TRUE;
83 // $compressionLevel can also be TRUE
84 if (t3lib_utility_Math::canBeInterpretedAsInteger($compressionLevel)) {
85 $this->gzipCompressionLevel = intval($compressionLevel);
86 }
87 }
88
89 $this->setInitialPaths();
90 }
91
92 /**
93 * Sets initial values for paths.
94 *
95 * @return void
96 */
97 public function setInitialPaths() {
98 $this->setInitialRelativePath();
99 $this->setInitialRootPath();
100 $this->setInitialBackPath();
101 }
102
103 /**
104 * Sets relative back path
105 *
106 * @return void
107 */
108 protected function setInitialBackPath() {
109 $backPath = (TYPO3_MODE === 'BE' ? $GLOBALS['BACK_PATH'] : '');
110 $this->setBackPath($backPath);
111 }
112
113 /**
114 * Sets absolute path to working directory
115 *
116 * @return void
117 */
118 protected function setInitialRootPath() {
119 $rootPath = (TYPO3_MODE === 'BE' ? PATH_typo3 : PATH_site);
120 $this->setRootPath($rootPath);
121 }
122
123 /**
124 * Sets relative path to PATH_site
125 *
126 * @return void
127 */
128 protected function setInitialRelativePath() {
129 $relativePath = (TYPO3_MODE === 'BE' ? $GLOBALS['BACK_PATH'] . '../' : '');
130 $this->setRelativePath($relativePath);
131 }
132
133 /**
134 * Sets relative path to PATH_site
135 *
136 * @param string $relativePath Relative path to site root
137 * @return void
138 */
139 public function setRelativePath($relativePath) {
140 if (is_string($relativePath)) {
141 $this->relativePath = $relativePath;
142 }
143 }
144
145 /**
146 * Sets absolute path to working directory
147 *
148 * @param string $rootPath Absolute path
149 * @return void
150 */
151 public function setRootPath($rootPath) {
152 if (is_string($rootPath)) {
153 $this->rootPath = $rootPath;
154 }
155 }
156
157 /**
158 * Sets relative back path
159 *
160 * @param string $backPath Back path
161 * @return void
162 */
163 public function setBackPath($backPath) {
164 if (is_string($backPath)) {
165 $this->backPath = $backPath;
166 }
167 }
168
169 /**
170 * Concatenates the Stylesheet files
171 *
172 * Options:
173 * baseDirectories If set, only include files below one of the base directories
174 *
175 * @param array $cssFiles CSS files to process
176 * @param array $options Additional options
177 * @return array CSS files
178 */
179 public function concatenateCssFiles(array $cssFiles, array $options = array()) {
180 $filesToInclude = array();
181 foreach ($cssFiles as $key => $fileOptions) {
182 // no concatenation allowed for this file, so continue
183 if (!empty($fileOptions['excludeFromConcatenation'])) {
184 continue;
185 }
186 // we remove BACK_PATH from $filename, so make it relative to root path
187 $filenameFromMainDir = $this->getFilenameFromMainDir($fileOptions['file']);
188 // if $options['baseDirectories'] set, we only include files below these directories
189 if ((!isset($options['baseDirectories'])
190 || $this->checkBaseDirectory($filenameFromMainDir, array_merge($options['baseDirectories'], array($this->targetDirectory))))
191 && ($fileOptions['media'] === 'all')
192 ) {
193 $filesToInclude[] = $filenameFromMainDir;
194 // remove the file from the incoming file array
195 unset($cssFiles[$key]);
196 }
197 }
198
199 if (count($filesToInclude)) {
200 $targetFile = $this->createMergedCssFile($filesToInclude);
201 $targetFileRelative = $this->relativePath . $targetFile;
202 $concatenatedOptions = array(
203 'file' => $targetFileRelative,
204 'rel' => 'stylesheet',
205 'media' => 'all',
206 'compress' => TRUE,
207 );
208 // place the merged stylesheet on top of the stylesheets
209 $cssFiles = array_merge(array($targetFileRelative => $concatenatedOptions), $cssFiles);
210 }
211 return $cssFiles;
212 }
213
214 /**
215 * Concatenates the JavaScript files
216 *
217 * @param array $jsFiles JavaScript files to process
218 * @return array JS files
219 */
220 public function concatenateJsFiles(array $jsFiles) {
221 $filesToInclude = array();
222 foreach ($jsFiles as $key => $fileOptions) {
223 // invalid section found or no concatenation allowed, so continue
224 if (empty($fileOptions['section']) || !empty($fileOptions['excludeFromConcatenation'])) {
225 continue;
226 }
227 // we remove BACK_PATH from $filename, so make it relative to root path
228 $filesToInclude[$fileOptions['section']][] = $this->getFilenameFromMainDir($fileOptions['file']);
229 // remove the file from the incoming file array
230 unset($jsFiles[$key]);
231 }
232
233 if (!empty($filesToInclude)) {
234 foreach ($filesToInclude as $section => $files) {
235 $targetFile = $this->createMergedJsFile($files);
236 $targetFileRelative = $this->relativePath . $targetFile;
237 $concatenatedOptions = array(
238 'file' => $targetFileRelative,
239 'type' => 'text/javascript',
240 'section' => $section,
241 'compress' => TRUE,
242 'forceOnTop' => FALSE,
243 'allWrap' => '',
244 );
245 // place the merged javascript on top of the JS files
246 $jsFiles = array_merge(array($targetFileRelative => $concatenatedOptions), $jsFiles);
247 }
248 }
249 return $jsFiles;
250 }
251
252 /**
253 * Creates a merged CSS file
254 *
255 * @param array $filesToInclude Files which should be merged, paths relative to root path
256 * @return mixed Filename of the merged file
257 */
258 protected function createMergedCssFile(array $filesToInclude) {
259 return $this->createMergedFile($filesToInclude, 'css');
260 }
261
262 /**
263 * Creates a merged JS file
264 *
265 * @param array $filesToInclude Files which should be merged, paths relative to root path
266 * @return mixed Filename of the merged file
267 */
268 protected function createMergedJsFile(array $filesToInclude) {
269 return $this->createMergedFile($filesToInclude, 'js');
270 }
271
272 /**
273 * Creates a merged file with given file type
274 *
275 * @param array $filesToInclude Files which should be merged, paths relative to root path
276 * @param string $type File type
277 * @return mixed Filename of the merged file
278 */
279 protected function createMergedFile(array $filesToInclude, $type = 'css') {
280 // Get file type
281 $type = strtolower(trim($type, '. '));
282 if (empty($type)) {
283 throw new InvalidArgumentException('Error in t3lib_Compressor: No valid file type given for merged file', 1308957498);
284 }
285
286 // we add up the filenames, filemtimes and filsizes to later build a checksum over
287 // it and include it in the temporary file name
288 $unique = '';
289
290 foreach ($filesToInclude as $key => $filename) {
291 if (t3lib_div::isValidUrl($filename)) {
292 $filesToInclude[$key] = $this->retrieveExternalFile($filename);
293 $filename = $filesToInclude[$key];
294 }
295 $filepath = t3lib_div::resolveBackPath($this->rootPath . $filename);
296 $unique .= $filename . filemtime($filepath) . filesize($filepath);
297 }
298 $targetFile = $this->targetDirectory . 'merged-' . md5($unique) . '.' . $type;
299
300 // if the file doesn't already exist, we create it
301 if (!file_exists(PATH_site . $targetFile)) {
302 $concatenated = '';
303 // concatenate all the files together
304 foreach ($filesToInclude as $filename) {
305 $contents = t3lib_div::getUrl(t3lib_div::resolveBackPath($this->rootPath . $filename));
306 // only fix paths if files aren't already in typo3temp (already processed)
307 if ($type === 'css' && !t3lib_div::isFirstPartOfStr($filename, $this->targetDirectory)) {
308 $contents = $this->cssFixRelativeUrlPaths($contents, dirname($filename) . '/');
309 }
310 $concatenated .= LF . $contents;
311 }
312 // move @charset, @import and @namespace statements to top of new file
313 if ($type === 'css') {
314 $concatenated = $this->cssFixStatements($concatenated);
315 }
316 t3lib_div::writeFile(PATH_site . $targetFile, $concatenated);
317 }
318 return $targetFile;
319 }
320
321 /**
322 * Compress multiple css files
323 *
324 * @param array $cssFiles The files to compress (array key = filename), relative to requested page
325 * @return array The CSS files after compression (array key = new filename), relative to requested page
326 */
327 public function compressCssFiles(array $cssFiles) {
328 $filesAfterCompression = array();
329 foreach ($cssFiles as $key => $fileOptions) {
330 // if compression is enabled
331 if ($fileOptions['compress']) {
332 $filename = $this->compressCssFile($fileOptions['file']);
333 $fileOptions['file'] = $filename;
334 $filesAfterCompression[$filename] = $fileOptions;
335 } else {
336 $filesAfterCompression[$key] = $fileOptions;
337 }
338 }
339 return $filesAfterCompression;
340 }
341
342 /**
343 * Compresses a CSS file
344 *
345 * Options:
346 * baseDirectories If set, only include files below one of the base directories
347 *
348 * removes comments and whitespaces
349 * Adopted from http://drupal.org/files/issues/minify_css.php__1.txt
350 *
351 * @param string $filename Source filename, relative to requested page
352 * @return string Compressed filename, relative to requested page
353 */
354 public function compressCssFile($filename) {
355 // generate the unique name of the file
356 $filenameAbsolute = t3lib_div::resolveBackPath($this->rootPath . $this->getFilenameFromMainDir($filename));
357 $unique = $filenameAbsolute . filemtime($filenameAbsolute) . filesize($filenameAbsolute);
358
359 $pathinfo = pathinfo($filename);
360 $targetFile = $this->targetDirectory . $pathinfo['filename'] . '-' . md5($unique) . '.css';
361 // only create it, if it doesn't exist, yet
362 if (!file_exists(PATH_site . $targetFile) || ($this->createGzipped && !file_exists(PATH_site . $targetFile . '.gzip'))) {
363 $contents = t3lib_div::getUrl($filenameAbsolute);
364 // Perform some safe CSS optimizations.
365 $contents = str_replace("\r", '', $contents); // Strip any and all carriage returns.
366 // Match and process strings, comments and everything else, one chunk at a time.
367 // To understand this regex, read: "Mastering Regular Expressions 3rd Edition" chapter 6.
368 $contents = preg_replace_callback('%
369 # One-regex-to-rule-them-all! - version: 20100220_0100
370 # Group 1: Match a double quoted string.
371 ("[^"\\\\]*+(?:\\\\.[^"\\\\]*+)*+") | # or...
372 # Group 2: Match a single quoted string.
373 (\'[^\'\\\\]*+(?:\\\\.[^\'\\\\]*+)*+\') | # or...
374 # Group 3: Match a regular non-MacIE5-hack comment.
375 (/\*[^\\\\*]*+\*++(?:[^\\\\*/][^\\\\*]*+\*++)*+/) | # or...
376 # Group 4: Match a MacIE5-type1 comment.
377 (/\*(?:[^*\\\\]*+\**+(?!/))*+\\\\[^*]*+\*++(?:[^*/][^*]*+\*++)*+/(?<!\\\\\*/)) | # or...
378 # Group 5: Match a MacIE5-type2 comment.
379 (/\*[^*]*\*+(?:[^/*][^*]*\*+)*/(?<=\\\\\*/)) # folllowed by...
380 # Group 6: Match everything up to final closing regular comment
381 ([^/]*+(?:(?!\*)/[^/]*+)*?)
382 # Group 7: Match final closing regular comment
383 (/\*[^/]++(?:(?<!\*)/(?!\*)[^/]*+)*+/(?<=(?<!\\\\)\*/)) | # or...
384 # Group 8: Match regular non-string, non-comment text.
385 ([^"\'/]*+(?:(?!/\*)/[^"\'/]*+)*+)
386 %Ssx', array('self', 'compressCssPregCallback'), $contents); // Do it!
387 $contents = preg_replace('/^\s++/', '', $contents); // Strip leading whitespace.
388 $contents = preg_replace('/[ \t]*+\n\s*+/S', "\n", $contents); // Consolidate multi-lines space.
389 $contents = preg_replace('/(?<!\s)\s*+$/S', "\n", $contents); // Ensure file ends in newline.
390 // we have to fix relative paths, if we aren't working on a file in our target directory
391 if (strpos($filename, $this->targetDirectory) === FALSE) {
392 $filenameRelativeToMainDir = substr($filename, strlen($this->backPath));
393 $contents = $this->cssFixRelativeUrlPaths($contents, dirname($filenameRelativeToMainDir) . '/');
394 }
395 $this->writeFileAndCompressed($targetFile, $contents);
396 }
397
398 return $this->relativePath . $this->returnFileReference($targetFile);
399 }
400
401 /**
402 * Callback function for preg_replace
403 *
404 * @see compressCssFile
405 * @param array $matches
406 * @return string the compressed string
407 */
408 public static function compressCssPregCallback($matches) {
409 if ($matches[1]) { // Group 1: Double quoted string.
410 return $matches[1]; // Return the string unmodified.
411 } elseif ($matches[2]) { // Group 2: Single quoted string.
412 return $matches[2]; // Return the string unmodified.
413 } elseif ($matches[3]) { // Group 3: Regular non-MacIE5-hack comment.
414 return "\n"; // Return single space.
415 } elseif ($matches[4]) { // Group 4: MacIE5-hack-type-1 comment.
416 return "\n/*\\T1*/\n"; // Return minimal MacIE5-hack-type-1 comment.
417 }
418 elseif ($matches[5]) { // Group 5,6,7: MacIE5-hack-type-2 comment
419 $matches[6] = preg_replace('/\s++([+>{};,)])/S', '$1', $matches[6]); // Clean pre-punctuation.
420 $matches[6] = preg_replace('/([+>{}:;,(])\s++/S', '$1', $matches[6]); // Clean post-punctuation.
421 $matches[6] = preg_replace('/;?\}/S', "}\n", $matches[6]); // Add a touch of formatting.
422 return "\n/*T2\\*/" . $matches[6] . "\n/*T2E*/\n"; // Minify and reassemble composite type2 comment.
423 } elseif (isset($matches[8])) { // Group 8: Non-string, non-comment. Safe to clean whitespace here.
424 $matches[8] = preg_replace('/^\s++/', '', $matches[8]); // Strip all leading whitespace.
425 $matches[8] = preg_replace('/\s++$/', '', $matches[8]); // Strip all trailing whitespace.
426 $matches[8] = preg_replace('/\s{2,}+/', ' ', $matches[8]); // Consolidate multiple whitespace.
427 $matches[8] = preg_replace('/\s++([+>{};,)])/S', '$1', $matches[8]); // Clean pre-punctuation.
428 $matches[8] = preg_replace('/([+>{}:;,(])\s++/S', '$1', $matches[8]); // Clean post-punctuation.
429 $matches[8] = preg_replace('/;?\}/S', "}\n", $matches[8]); // Add a touch of formatting.
430 return $matches[8];
431 }
432 return $matches[0] . "\n/* ERROR! Unexpected _proccess_css_minify() parameter */\n"; // never get here
433 }
434
435 /**
436 * Compress multiple javascript files
437 *
438 * @param array $jsFiles The files to compress (array key = filename), relative to requested page
439 * @return array The js files after compression (array key = new filename), relative to requested page
440 */
441 public function compressJsFiles(array $jsFiles) {
442 $filesAfterCompression = array();
443 foreach ($jsFiles as $key => $fileOptions) {
444 // if compression is enabled
445 if ($fileOptions['compress']) {
446 $fileOptions['file'] = $this->compressJsFile($fileOptions['file']);
447 }
448 $filesAfterCompression[$key] = $fileOptions;
449 }
450 return $filesAfterCompression;
451 }
452
453 /**
454 * Compresses a javascript file
455 *
456 * @param string $filename Source filename, relative to requested page
457 * @return string Filename of the compressed file, relative to requested page
458 */
459 public function compressJsFile($filename) {
460 // generate the unique name of the file
461 $filenameAbsolute = t3lib_div::resolveBackPath($this->rootPath . $this->getFilenameFromMainDir($filename));
462 $unique = $filenameAbsolute . filemtime($filenameAbsolute) . filesize($filenameAbsolute);
463
464 $pathinfo = pathinfo($filename);
465 $targetFile = $this->targetDirectory . $pathinfo['filename'] . '-' . md5($unique) . '.js';
466 // only create it, if it doesn't exist, yet
467 if (!file_exists(PATH_site . $targetFile) || ($this->createGzipped && !file_exists(PATH_site . $targetFile . '.gzip'))) {
468 $contents = t3lib_div::getUrl($filenameAbsolute);
469 $this->writeFileAndCompressed($targetFile, $contents);
470 }
471 return $this->relativePath . $this->returnFileReference($targetFile);
472 }
473
474 /**
475 * Finds the relative path to a file, relative to the root path.
476 *
477 * @param string $filename the name of the file
478 * @return string the path to the file relative to the root path
479 */
480 protected function getFilenameFromMainDir($filename) {
481 // if BACK_PATH is empty return $filename
482 if (empty($this->backPath)) {
483 return $filename;
484 }
485
486 // if the file exists in the root path, just return the $filename
487 if (strpos($filename, $this->backPath) === 0) {
488 $file = str_replace($this->backPath, '', $filename);
489 if (is_file($this->rootPath . $file)) {
490 return $file;
491 }
492 }
493
494 // build the file path relatively to the PATH_site
495 $backPath = str_replace(TYPO3_mainDir, '', $this->backPath);
496 $file = str_replace($backPath, '', $filename);
497 if (substr($file, 0, 3) === '../') {
498 $file = t3lib_div::resolveBackPath(PATH_typo3 . $file);
499 } else {
500 $file = PATH_site . $file;
501 }
502
503 // check if the file exists, and if so, return the path relative to TYPO3_mainDir
504 if (is_file($file)) {
505 $mainDirDepth = substr_count(TYPO3_mainDir, '/');
506 return str_repeat('../', $mainDirDepth) . str_replace(PATH_site, '', $file);
507 }
508
509 // none of above conditions were met, fallback to default behaviour
510 return substr($filename, strlen($this->backPath));
511 }
512
513 /**
514 * Decides whether a file comes from one of the baseDirectories
515 *
516 * @param string $filename Filename
517 * @param array $baseDirectories Base directories
518 * @return boolean File belongs to a base directory or not
519 */
520 protected function checkBaseDirectory($filename, array $baseDirectories) {
521 foreach ($baseDirectories as $baseDirectory) {
522 // check, if $filename starts with base directory
523 if (t3lib_div::isFirstPartOfStr($filename, $baseDirectory)) {
524 return TRUE;
525 }
526 }
527 return FALSE;
528 }
529
530 /**
531 * Fixes the relative paths inside of url() references in CSS files
532 *
533 * @param string $contents Data to process
534 * @param string $oldDir Directory of the original file, relative to TYPO3_mainDir
535 * @return string Processed data
536 */
537 protected function cssFixRelativeUrlPaths($contents, $oldDir) {
538 $mainDir = (TYPO3_MODE === 'BE' ? TYPO3_mainDir : '');
539 $newDir = '../../' . $mainDir . $oldDir;
540
541 // Replace "url()" paths
542 if (stripos($contents, 'url') !== FALSE) {
543 $regex = '/url(\(\s*["\']?(?!\/)([^"\']+)["\']?\s*\))/iU';
544 $contents = $this->findAndReplaceUrlPathsByRegex($contents, $regex, $newDir, '(\'|\')');
545 }
546
547 // Replace "@import" paths
548 if (stripos($contents, '@import') !== FALSE) {
549 $regex = '/@import\s*(["\']?(?!\/)([^"\']+)["\']?)/i';
550 $contents = $this->findAndReplaceUrlPathsByRegex($contents, $regex, $newDir, '"|"');
551 }
552
553 return $contents;
554 }
555
556 /**
557 * Finds and replaces all URLs by using a given regex
558 *
559 * @param string $contents Data to process
560 * @param string $regex Regex used to find URLs in content
561 * @param string $newDir Path to prepend to the original file
562 * @param string $wrap Wrap around replaced values
563 * @return string Processed data
564 */
565 protected function findAndReplaceUrlPathsByRegex($contents, $regex, $newDir, $wrap = '|') {
566 $matches = array();
567 $replacements = array();
568 $wrap = explode('|', $wrap);
569
570 preg_match_all($regex, $contents, $matches);
571 foreach ($matches[2] as $matchCount => $match) {
572 // remove '," or white-spaces around
573 $match = trim($match, '\'" ');
574
575 // we must not rewrite paths containing ":" or "url(", e.g. data URIs (see RFC 2397)
576 if (strpos($match, ':') === FALSE && !preg_match('/url\s*\(/i', $match)) {
577 $newPath = t3lib_div::resolveBackPath($newDir . $match);
578 $replacements[$matches[1][$matchCount]] = $wrap[0] . $newPath . $wrap[1];
579 }
580 }
581
582 // replace URL paths in content
583 if (!empty($replacements)) {
584 $contents = str_replace(array_keys($replacements), array_values($replacements), $contents);
585 }
586
587 return $contents;
588 }
589
590 /**
591 * Moves @charset, @import and @namespace statements to the top of
592 * the content, because they must occur before all other CSS rules
593 *
594 * @param string $contents Data to process
595 * @return string Processed data
596 */
597 protected function cssFixStatements($contents) {
598 $matches = array();
599 $comment = LF . '/* moved by compressor */' . LF;
600
601 // nothing to do, so just return contents
602 if (stripos($contents, '@charset') === FALSE && stripos($contents, '@import') === FALSE
603 && stripos($contents, '@namespace') === FALSE) {
604
605 return $contents;
606 }
607
608 $regex = '/@(charset|import|namespace)\s*(url)?\s*\(?\s*["\']?[^"\']+["\']?\s*\)?.*;/i';
609 preg_match_all($regex, $contents, $matches);
610 if (!empty($matches[0])) {
611 // remove existing statements
612 $contents = str_replace($matches[0], '', $contents);
613 // add statements to the top of contents in the order they occur in original file
614 $contents = $comment . implode($comment, $matches[0]) . LF . $contents;
615 }
616
617 return $contents;
618 }
619
620 /**
621 * Writes $contents into file $filename together with a gzipped version into $filename.gz
622 *
623 * @param string $filename Target filename
624 * @param string $contents File contents
625 * @return void
626 */
627 protected function writeFileAndCompressed($filename, $contents) {
628 // write uncompressed file
629 t3lib_div::writeFile(PATH_site . $filename, $contents);
630
631 if ($this->createGzipped) {
632 // create compressed version
633 t3lib_div::writeFile(PATH_site . $filename . '.gzip', gzencode($contents, $this->gzipCompressionLevel));
634 }
635 }
636
637 /**
638 * Decides whether a client can deal with gzipped content or not and returns the according file name,
639 * based on HTTP_ACCEPT_ENCODING
640 *
641 * @param string $filename File name
642 * @return string $filename suffixed with '.gzip' or not - dependent on HTTP_ACCEPT_ENCODING
643 */
644 protected function returnFileReference($filename) {
645 // if the client accepts gzip and we can create gzipped files, we give him compressed versions
646 if ($this->createGzipped && strpos(t3lib_div::getIndpEnv('HTTP_ACCEPT_ENCODING'), 'gzip') !== FALSE) {
647 return $filename . '.gzip';
648 } else {
649 return $filename;
650 }
651 }
652
653 /**
654 * Retrieves an external file and stores it locally.
655 *
656 * @param string $url
657 * @return string Temporary local filename for the externally-retrieved file
658 */
659 protected function retrieveExternalFile($url) {
660 $externalContent = t3lib_div::getUrl($url);
661 $filename = $this->targetDirectory . 'external-' . md5($url);
662 t3lib_div::writeFile(PATH_site . $filename, $externalContent);
663 return $filename;
664 }
665
666 }
667
668 if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_compressor.php'])) {
669 include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_compressor.php']);
670 }
671
672 ?>