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