54 <IfModule mod_expires.c>
56 ExpiresDefault "access plus 7 days"
71 if (
$GLOBALS[
'TYPO3_CONF_VARS'][
'SYS'][
'generateApacheHtaccess']) {
74 if (!file_exists($htaccessPath)) {
75 GeneralUtility::writeFile($htaccessPath, $this->htaccessTemplate);
79 $compressionLevel =
$GLOBALS[
'TYPO3_CONF_VARS'][TYPO3_MODE][
'compressionLevel'];
81 if (extension_loaded(
'zlib') && $compressionLevel) {
82 $this->createGzipped =
true;
85 $this->gzipCompressionLevel = (int)$compressionLevel;
115 $filesToIncludeByType = [
'all' => []];
116 foreach ($cssFiles as $key => $fileOptions) {
118 if (!empty($fileOptions[
'excludeFromConcatenation'])) {
124 !isset($options[
'baseDirectories'])
126 $filenameFromMainDir,
127 array_merge($options[
'baseDirectories'], [$this->targetDirectory])
130 $type = isset($fileOptions[
'media']) ? strtolower($fileOptions[
'media']) :
'all';
131 if (!isset($filesToIncludeByType[$type])) {
132 $filesToIncludeByType[$type] = [];
134 if (!empty($fileOptions[
'forceOnTop'])) {
135 array_unshift($filesToIncludeByType[$type], $filenameFromMainDir);
137 $filesToIncludeByType[$type][] = $filenameFromMainDir;
140 unset($cssFiles[$key]);
143 if (!empty($filesToIncludeByType)) {
144 foreach ($filesToIncludeByType as $mediaOption => $filesToInclude) {
145 if (empty($filesToInclude)) {
149 $concatenatedOptions = [
150 'file' => $targetFile,
151 'rel' =>
'stylesheet',
152 'media' => $mediaOption,
154 'excludeFromConcatenation' =>
true,
155 'forceOnTop' =>
false,
159 $cssFiles = array_merge($cssFiles, [$targetFile => $concatenatedOptions]);
173 $concatenatedJsFileIsAsync =
false;
174 $allFilesToConcatenateAreAsync =
true;
175 $filesToInclude = [];
176 foreach ($jsFiles as $key => $fileOptions) {
178 if (empty($fileOptions[
'section']) || !empty($fileOptions[
'excludeFromConcatenation']) || !empty($fileOptions[
'defer'])) {
181 if (!isset($filesToInclude[$fileOptions[
'section']])) {
182 $filesToInclude[$fileOptions[
'section']] = [];
185 if (!empty($fileOptions[
'forceOnTop'])) {
186 array_unshift($filesToInclude[$fileOptions[
'section']], $filenameFromMainDir);
188 $filesToInclude[$fileOptions[
'section']][] = $filenameFromMainDir;
190 if (!empty($fileOptions[
'async']) && (
bool)$fileOptions[
'async']) {
191 $concatenatedJsFileIsAsync =
true;
193 $allFilesToConcatenateAreAsync =
false;
196 unset($jsFiles[$key]);
198 if (!empty($filesToInclude)) {
199 foreach ($filesToInclude as $section => $files) {
201 $concatenatedOptions = [
202 'file' => $targetFile,
203 'type' =>
'text/javascript',
204 'section' => $section,
206 'excludeFromConcatenation' =>
true,
207 'forceOnTop' =>
false,
209 'async' => $concatenatedJsFileIsAsync && $allFilesToConcatenateAreAsync,
212 $jsFiles = array_merge([$targetFile => $concatenatedOptions], $jsFiles);
252 $type = strtolower(trim($type,
'. '));
254 throw new \InvalidArgumentException(
'No valid file type given for files to be merged.', 1308957498);
259 foreach ($filesToInclude as $key => $filename) {
260 if (GeneralUtility::isValidUrl($filename)) {
262 if (GeneralUtility::isOnCurrentHost($filename) &&
263 GeneralUtility::isFirstPartOfStr(
265 GeneralUtility::getIndpEnv(
'TYPO3_SITE_URL')
269 $localFilename = substr($filename, strlen(GeneralUtility::getIndpEnv(
'TYPO3_SITE_URL')));
270 if (@is_file(GeneralUtility::resolveBackPath($this->rootPath . $localFilename))) {
271 $filesToInclude[$key] = $localFilename;
278 $filename = $filesToInclude[$key];
280 $filenameAbsolute = GeneralUtility::resolveBackPath($this->rootPath . $filename);
281 if (@file_exists($filenameAbsolute)) {
282 $fileStatus = stat($filenameAbsolute);
283 $unique .= $filenameAbsolute . $fileStatus[
'mtime'] . $fileStatus[
'size'];
285 $unique .= $filenameAbsolute;
288 $targetFile = $this->targetDirectory .
'merged-' . md5($unique) .
'.' . $type;
293 foreach ($filesToInclude as $filename) {
294 $filenameAbsolute = GeneralUtility::resolveBackPath($this->rootPath . $filename);
296 $contents = file_get_contents($filenameAbsolute);
298 if (strpos($contents,
"\xEF\xBB\xBF") === 0) {
299 $contents = substr($contents, 3);
302 if ($type ===
'css' && !GeneralUtility::isFirstPartOfStr($filename, $this->targetDirectory)) {
305 $concatenated .= LF . $contents;
308 if ($type ===
'css') {
324 $filesAfterCompression = [];
325 foreach ($cssFiles as $key => $fileOptions) {
327 if ($fileOptions[
'compress']) {
329 $fileOptions[
'compress'] =
false;
330 $fileOptions[
'file'] = $filename;
331 $filesAfterCompression[$filename] = $fileOptions;
333 $filesAfterCompression[$key] = $fileOptions;
336 return $filesAfterCompression;
354 $filenameAbsolute = GeneralUtility::resolveBackPath($this->rootPath . $this->
getFilenameFromMainDir($filename));
355 if (@file_exists($filenameAbsolute)) {
356 $fileStatus = stat($filenameAbsolute);
357 $unique = $filenameAbsolute . $fileStatus[
'mtime'] . $fileStatus[
'size'];
359 $unique = $filenameAbsolute;
365 $targetFile = $this->targetDirectory . $pathinfo[
'filename'] .
'-' . md5($unique) .
'.css';
369 if (strpos($filename, $this->targetDirectory) ===
false) {
385 $filesAfterCompression = [];
386 foreach ($jsFiles as $fileName => $fileOptions) {
388 if ($fileOptions[
'compress']) {
390 $fileOptions[
'compress'] =
false;
391 $fileOptions[
'file'] = $compressedFilename;
392 $filesAfterCompression[$compressedFilename] = $fileOptions;
394 $filesAfterCompression[$fileName] = $fileOptions;
397 return $filesAfterCompression;
409 $filenameAbsolute = GeneralUtility::resolveBackPath($this->rootPath . $this->
getFilenameFromMainDir($filename));
410 if (@file_exists($filenameAbsolute)) {
411 $fileStatus = stat($filenameAbsolute);
412 $unique = $filenameAbsolute . $fileStatus[
'mtime'] . $fileStatus[
'size'];
414 $unique = $filenameAbsolute;
417 $targetFile = $this->targetDirectory . $pathinfo[
'filename'] .
'-' . md5($unique) .
'.js';
420 $contents = file_get_contents($filenameAbsolute);
445 $docRoot = GeneralUtility::getIndpEnv(
'TYPO3_DOCUMENT_ROOT');
446 $fileNameWithoutSlash = ltrim($filename,
'/');
449 $absolutePath = $docRoot .
'/' . $fileNameWithoutSlash;
454 $absolutePath = $filename;
456 if (@is_file($absolutePath)) {
457 if (strpos($absolutePath, $this->rootPath) === 0) {
459 return substr($absolutePath, strlen($this->rootPath));
466 if (is_file($this->rootPath . $fileNameWithoutSlash)) {
467 return $fileNameWithoutSlash;
471 $filename =
'typo3/' . $filename;
474 if (strpos($filename,
'EXT:') === 0) {
475 $file = GeneralUtility::getFileAbsFileName($filename);
476 } elseif (strpos($filename,
'../') === 0) {
483 if (is_file($file)) {
499 foreach ($baseDirectories as $baseDirectory) {
501 if (GeneralUtility::isFirstPartOfStr($filename, $baseDirectory)) {
517 $newDir =
'../../../' . $oldDir;
519 if (stripos($contents,
'url') !==
false) {
520 $regex =
'/url(\\(\\s*["\']?(?!\\/)([^"\']+)["\']?\\s*\\))/iU';
524 if (stripos($contents,
'@import') !==
false) {
525 $regex =
'/@import\\s*(["\']?(?!\\/)([^"\']+)["\']?)/i';
544 $wrap = explode(
'|', $wrap);
545 preg_match_all($regex, $contents, $matches);
546 foreach ($matches[2] as $matchCount => $match) {
548 $match = trim($match,
'\'" ');
549 // we must not rewrite paths containing ":
" or "url(
", e.g. data URIs (see RFC 2397)
550 if (strpos($match, ':') === false && !preg_match('/url\\s*\\(/i', $match)) {
551 $newPath = GeneralUtility::resolveBackPath($newDir . $match);
552 $replacements[$matches[1][$matchCount]] = $wrap[0] . $newPath . $wrap[1];
555 // replace URL paths in content
556 if (!empty($replacements)) {
557 $contents = str_replace(array_keys($replacements), array_values($replacements), $contents);
569 protected function cssFixStatements($contents)
572 $comment = LF . '/* moved by compressor */' . LF;
573 // nothing to do, so just return contents
574 if (stripos($contents, '@charset') === false && stripos($contents, '@import') === false && stripos($contents, '@namespace') === false) {
577 $regex = '/@(charset|import|namespace)\\s*(url)?\\s*\\(?\\s*["\
']?[^"\'\\)]+["\']?\\s*\\)?\\s*;/i';
578 preg_match_all($regex, $contents, $matches);
579 if (!empty($matches[0])) {
584 foreach ($matches[1] as $index => $keyword) {
587 if (empty($charset)) {
588 $charset = $matches[0][$index];
592 $namespaces[] = $matches[0][$index];
595 $imports[] = $matches[0][$index];
600 $namespaces = !empty($namespaces) ? implode(
'', $namespaces) . $comment :
'';
601 $imports = !empty($imports) ? implode(
'', $imports) . $comment :
'';
603 $contents = str_replace($matches[0],
'', $contents);
625 if ($this->createGzipped) {
627 GeneralUtility::writeFile(
Environment::getPublicPath() .
'/' . $filename .
'.gzip', gzencode($contents, $this->gzipCompressionLevel));
641 if ($this->createGzipped && strpos(GeneralUtility::getIndpEnv(
'HTTP_ACCEPT_ENCODING'),
'gzip') !==
false) {
642 $filename .=
'.gzip';
655 $externalContent = GeneralUtility::getUrl($url);
656 $filename = $this->targetDirectory .
'external-' . md5($url);
676 $comment =
'/\*[^*]*\*+(?:[^/*][^*]*\*+)*/';
678 $double_quot =
'"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"';
680 $single_quot =
"'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'";
682 $contents = preg_replace(
683 "<($double_quot|$single_quot)|$comment>Ss",
691 $contents = preg_replace(
693 # Strip leading and trailing whitespace.
695 # Strip only leading whitespace from:
696 # - Closing parenthesis: Retain "@media (bar) and foo".
698 # Strip only trailing whitespace from:
699 # - Opening parenthesis: Retain "@media (bar) and foo".
700 # - Colon: Retain :pseudo-selectors.
710 $contents = trim($contents);