Initial commit
This commit is contained in:
145
typo3conf/ext/scriptmerger/Classes/ScriptmergerBase.php
Normal file
145
typo3conf/ext/scriptmerger/Classes/ScriptmergerBase.php
Normal file
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
namespace SGalinski\Scriptmerger;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) Stefan Galinski <stefan@sgalinski.de>
|
||||
* All rights reserved
|
||||
*
|
||||
* This script is part of the TYPO3 project. The TYPO3 project is
|
||||
* free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The GNU General Public License can be found at
|
||||
* http://www.gnu.org/copyleft/gpl.html.
|
||||
*
|
||||
* This script is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* This class contains the basic stuff required by both processors, css and javascript.
|
||||
*/
|
||||
abstract class ScriptmergerBase {
|
||||
/**
|
||||
* directories for minified, compressed and merged files
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $tempDirectories = '';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $configuration;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->tempDirectories = array(
|
||||
'main' => PATH_site . 'typo3temp/scriptmerger/',
|
||||
'temp' => PATH_site . 'typo3temp/scriptmerger/temp/',
|
||||
'minified' => PATH_site . 'typo3temp/scriptmerger/uncompressed/',
|
||||
'compressed' => PATH_site . 'typo3temp/scriptmerger/compressed/',
|
||||
'merged' => PATH_site . 'typo3temp/scriptmerger/uncompressed/'
|
||||
);
|
||||
|
||||
foreach ($this->tempDirectories as $directory) {
|
||||
if (!is_dir($directory)) {
|
||||
GeneralUtility::mkdir($directory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects the extension configuration
|
||||
*
|
||||
* @param array $configuration
|
||||
* @return void
|
||||
*/
|
||||
public function injectExtensionConfiguration(array $configuration) {
|
||||
$this->configuration = $configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Controller for the dedicated script type processing
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract function process();
|
||||
|
||||
/**
|
||||
* Gets a file from an external resource (e.g. http://) and caches them
|
||||
*
|
||||
* @param string $source Source address
|
||||
* @param boolean $returnContent
|
||||
* @return string cache file or content (depends on the parameter)
|
||||
*/
|
||||
protected function getExternalFile($source, $returnContent = FALSE) {
|
||||
$filename = basename($source);
|
||||
$hash = md5($source);
|
||||
$cacheFile = $this->tempDirectories['temp'] . $filename . '-' . $hash;
|
||||
$externalFileCacheLifetime = intval($this->configuration['externalFileCacheLifetime']);
|
||||
$cacheLifetime = ($externalFileCacheLifetime > 0) ? $externalFileCacheLifetime : 3600;
|
||||
|
||||
// check the age of the cache file (also fails with non-existent file)
|
||||
$content = '';
|
||||
if ((int) @filemtime($cacheFile) <= ($GLOBALS['EXEC_TIME'] - $cacheLifetime)) {
|
||||
if ($source{0} === '/' && $source{1} === '/') {
|
||||
$protocol = stripos($_SERVER['SERVER_PROTOCOL'], 'https') === TRUE ? 'https:' : 'http:';
|
||||
$source = $protocol . $source;
|
||||
}
|
||||
|
||||
$content = GeneralUtility::getURL($source);
|
||||
if ($content !== FALSE) {
|
||||
$this->writeFile($cacheFile, $content);
|
||||
} else {
|
||||
$cacheFile = '';
|
||||
}
|
||||
} elseif ($returnContent) {
|
||||
$content = file_get_contents($cacheFile);
|
||||
}
|
||||
|
||||
$returnValue = $cacheFile;
|
||||
if ($returnContent) {
|
||||
$returnValue = $content;
|
||||
}
|
||||
|
||||
return $returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes $content to the file $file
|
||||
*
|
||||
* @param string $file file path to write to
|
||||
* @param string $content Content to write
|
||||
* @return boolean TRUE if the file was successfully opened and written to.
|
||||
*/
|
||||
protected function writeFile($file, $content) {
|
||||
$result = GeneralUtility::writeFile($file, $content);
|
||||
|
||||
// hook here for other file system operations like syncing to other servers etc.
|
||||
$hooks = $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['scriptmerger']['writeFilePostHook'];
|
||||
if (is_array($hooks)) {
|
||||
foreach ($hooks as $classReference) {
|
||||
$hookObject = GeneralUtility::getUserObj($classReference);
|
||||
$hookObject->writeFilePostHook($file, $content, $this);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace SGalinski\Scriptmerger;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) Stefan Galinski <stefan@sgalinski.de>
|
||||
* All rights reserved
|
||||
*
|
||||
* This script is part of the TYPO3 project. The TYPO3 project is
|
||||
* free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The GNU General Public License can be found at
|
||||
* http://www.gnu.org/copyleft/gpl.html.
|
||||
*
|
||||
* This script is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
|
||||
/**
|
||||
* This class contains the basic stuff required for preserving conditional comments
|
||||
*/
|
||||
class ScriptmergerConditionalCommentPreserver {
|
||||
/**
|
||||
* holds the conditional comments
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $conditionalComments = array();
|
||||
|
||||
/**
|
||||
* Callback function to replace conditional comments with placeholders
|
||||
*
|
||||
* @param array $hits
|
||||
* @return string
|
||||
*/
|
||||
protected function save($hits) {
|
||||
$this->conditionalComments[] = $hits[0];
|
||||
return '###conditionalComment' . (count($this->conditionalComments) - 1) . '###';
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback function to restore placeholders for conditional comments
|
||||
*
|
||||
* @param array $hits
|
||||
* @return string
|
||||
*/
|
||||
protected function restore($hits) {
|
||||
$results = array();
|
||||
preg_match('/\d+/is', $hits[0], $results);
|
||||
$result = '';
|
||||
if (count($results) > 0) {
|
||||
$result = $this->conditionalComments[$results[0]];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method parses the output content and saves any found conditional comments
|
||||
* into the "conditionalComments" class property. The output content is cleaned
|
||||
* up of the found results.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function read() {
|
||||
$pattern = '/<!--\[if.+?<!\[endif\]-->/is';
|
||||
$GLOBALS['TSFE']->content = preg_replace_callback(
|
||||
$pattern,
|
||||
array($this, 'save'),
|
||||
$GLOBALS['TSFE']->content
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method writes the conditional comments back into the final output content.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function writeBack() {
|
||||
$pattern = '/###conditionalComment\d+###/is';
|
||||
$GLOBALS['TSFE']->content = preg_replace_callback(
|
||||
$pattern,
|
||||
array($this, 'restore'),
|
||||
$GLOBALS['TSFE']->content
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
453
typo3conf/ext/scriptmerger/Classes/ScriptmergerCss.php
Normal file
453
typo3conf/ext/scriptmerger/Classes/ScriptmergerCss.php
Normal file
@@ -0,0 +1,453 @@
|
||||
<?php
|
||||
|
||||
namespace SGalinski\Scriptmerger;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) Stefan Galinski <stefan@sgalinski.de>
|
||||
* All rights reserved
|
||||
*
|
||||
* This script is part of the TYPO3 project. The TYPO3 project is
|
||||
* free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The GNU General Public License can be found at
|
||||
* http://www.gnu.org/copyleft/gpl.html.
|
||||
*
|
||||
* This script is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
|
||||
|
||||
/**
|
||||
* This class contains the parsing and replacing functionality for css files
|
||||
*/
|
||||
class ScriptmergerCss extends ScriptmergerBase {
|
||||
/**
|
||||
* holds the javascript code
|
||||
*
|
||||
* Structure:
|
||||
* - $relation (rel attribute)
|
||||
* - $media (media attribute)
|
||||
* - $file
|
||||
* |-content => string
|
||||
* |-basename => string (base name of $file without file prefix)
|
||||
* |-minify-ignore => bool
|
||||
* |-merge-ignore => bool
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $css = array();
|
||||
|
||||
/**
|
||||
* Injects the extension configuration
|
||||
*
|
||||
* @param array $configuration
|
||||
* @return void
|
||||
*/
|
||||
public function injectExtensionConfiguration(array $configuration) {
|
||||
parent::injectExtensionConfiguration($configuration);
|
||||
|
||||
if (!class_exists('Minify_ImportProcessor', FALSE)) {
|
||||
require_once(ExtensionManagementUtility::extPath('scriptmerger') . 'Resources/Minify/ImportProcessor.php');
|
||||
}
|
||||
\Minify_ImportProcessor::$extensionConfiguration = $this->configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Controller for the css parsing and replacement
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function process() {
|
||||
// fetch all remaining css contents
|
||||
$this->getFiles();
|
||||
|
||||
// minify, compress and merging
|
||||
foreach ($this->css as $relation => $cssByRelation) {
|
||||
foreach ($cssByRelation as $media => $cssByMedia) {
|
||||
$mergedContent = '';
|
||||
$positionOfMergedFile = NULL;
|
||||
foreach ($cssByMedia as $index => $cssProperties) {
|
||||
$newFile = '';
|
||||
|
||||
// file should be minified
|
||||
if ($this->configuration['css.']['minify.']['enable'] === '1' &&
|
||||
!$cssProperties['minify-ignore']
|
||||
) {
|
||||
$newFile = $this->minifyFile($cssProperties);
|
||||
}
|
||||
|
||||
// file should be merged
|
||||
if ($this->configuration['css.']['merge.']['enable'] === '1' &&
|
||||
!$cssProperties['merge-ignore']
|
||||
) {
|
||||
if ($positionOfMergedFile === NULL) {
|
||||
$positionOfMergedFile = $cssProperties['position-key'];
|
||||
}
|
||||
|
||||
$mergedContent .= $cssProperties['content'] . LF;
|
||||
unset($this->css[$relation][$media][$index]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// file should be compressed instead?
|
||||
if ($this->configuration['css.']['compress.']['enable'] === '1' &&
|
||||
function_exists('gzcompress') && !$cssProperties['compress-ignore']
|
||||
) {
|
||||
$newFile = $this->compressFile($cssProperties);
|
||||
}
|
||||
|
||||
// minification or compression was used
|
||||
if ($newFile !== '') {
|
||||
$this->css[$relation][$media][$index]['file'] = $newFile;
|
||||
$this->css[$relation][$media][$index]['content'] =
|
||||
$cssProperties['content'];
|
||||
$this->css[$relation][$media][$index]['basename'] =
|
||||
$cssProperties['basename'];
|
||||
}
|
||||
}
|
||||
|
||||
// save merged content inside a new file
|
||||
if ($this->configuration['css.']['merge.']['enable'] === '1' && $mergedContent !== '') {
|
||||
if ($this->configuration['css.']['uniqueCharset.']['enable'] === '1') {
|
||||
$mergedContent = $this->uniqueCharset($mergedContent);
|
||||
}
|
||||
|
||||
// create property array
|
||||
$properties = array(
|
||||
'content' => $mergedContent,
|
||||
'basename' => 'head-' . md5($mergedContent) . '.merged'
|
||||
);
|
||||
|
||||
// write merged file in any case
|
||||
$newFile = $this->tempDirectories['merged'] . $properties['basename'] . '.css';
|
||||
if (!file_exists($newFile)) {
|
||||
$this->writeFile($newFile, $properties['content']);
|
||||
}
|
||||
|
||||
// file should be compressed
|
||||
if ($this->configuration['css.']['compress.']['enable'] === '1' &&
|
||||
function_exists('gzcompress')
|
||||
) {
|
||||
$newFile = $this->compressFile($properties);
|
||||
}
|
||||
|
||||
// add new entry
|
||||
$this->css[$relation][$media][] = array(
|
||||
'file' => $newFile,
|
||||
'content' => $properties['content'],
|
||||
'basename' => $properties['basename'],
|
||||
'position-key' => $positionOfMergedFile,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// write the conditional comments and possibly merged css files back to the document
|
||||
$this->writeToDocument();
|
||||
}
|
||||
|
||||
/**
|
||||
* Some browser fail on parsing merged CSS files if multiple charset definitions are found.
|
||||
* Therefor we replace all charset definition's with an empty string and add a single charset
|
||||
* definition to the beginning of the content. At least Webkit engines fail badly.
|
||||
*
|
||||
* @param string $content
|
||||
* @return string
|
||||
*/
|
||||
protected function uniqueCharset($content) {
|
||||
if (!empty($this->configuration['css.']['uniqueCharset.']['value'])) {
|
||||
$content = preg_replace('/@charset[^;]+;/', '', $content);
|
||||
$content = $this->configuration['css.']['uniqueCharset.']['value'] . $content;
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method parses the output content and saves any found css files or inline code
|
||||
* into the "css" class property. The output content is cleaned up of the found results.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function getFiles() {
|
||||
// filter pattern for the inDoc styles (fetches the content)
|
||||
$filterInDocumentPattern = '/' .
|
||||
'<style.*?>' . // This expression removes the opening style tag
|
||||
'(?:.*?\/\*<!\[CDATA\[\*\/)?' . // and the optionally prefixed CDATA string.
|
||||
'\s*(.*?)' . // We save the pure css content,
|
||||
'(?:\s*\/\*\]\]>\*\/)?' . // remove the possible closing CDATA string
|
||||
'\s*<\/style>' . // and closing style tag
|
||||
'/is';
|
||||
|
||||
// parse all available css code inside link and style tags
|
||||
$cssTags = array();
|
||||
$pattern = '/' .
|
||||
'<(link|sty)' . // Parse any link and style tags.
|
||||
'(?=.+?(?:media="(.*?)"|>))' . // Fetch the media attribute
|
||||
'(?=.+?(?:href="(.*?)"|>))' . // and the href attribute
|
||||
'(?=.+?(?:rel="(.*?)"|>))' . // and the rel attribute
|
||||
'(?=.+?(?:title="(.*?)"|>))' . // and the title attribute of the tag.
|
||||
'(?=.+?(?:data-ignore="(.*?)"|>))' . // and the data-ignore attribute of the tag.
|
||||
'(?:[^>]+?\.css[^>]+?\/?>' . // Continue parsing from \1 to the closing tag.
|
||||
'|le[^>]*?>[^>]+?<\/style>)\s*' .
|
||||
'/is';
|
||||
|
||||
preg_match_all($pattern, $GLOBALS['TSFE']->content, $cssTags);
|
||||
$amountOfResults = count($cssTags[0]);
|
||||
if (!$amountOfResults) {
|
||||
return;
|
||||
}
|
||||
|
||||
$function = create_function('', 'static $i = 0; return \'###MERGER\' . $i++ . \'MERGER###\';');
|
||||
$GLOBALS['TSFE']->content = preg_replace_callback(
|
||||
$pattern, $function, $GLOBALS['TSFE']->content, $amountOfResults
|
||||
);
|
||||
|
||||
for ($i = 0; $i < $amountOfResults; ++$i) {
|
||||
$content = '';
|
||||
|
||||
$media = (trim($cssTags[2][$i]) === '') ? 'all' : $cssTags[2][$i];
|
||||
$media = implode(',', array_map('trim', explode(',', $media)));
|
||||
|
||||
$relation = (trim($cssTags[4][$i]) === '') ? 'stylesheet' : $cssTags[4][$i];
|
||||
$source = $cssTags[3][$i];
|
||||
$title = trim($cssTags[5][$i]);
|
||||
$ignoreDataFlagSet = intval($cssTags[6][$i]);
|
||||
|
||||
// add basic entry
|
||||
$this->css[$relation][$media][$i]['minify-ignore'] = FALSE;
|
||||
$this->css[$relation][$media][$i]['compress-ignore'] = FALSE;
|
||||
$this->css[$relation][$media][$i]['merge-ignore'] = FALSE;
|
||||
$this->css[$relation][$media][$i]['addInDocument'] = FALSE;
|
||||
$this->css[$relation][$media][$i]['useOriginalCodeLine'] = FALSE;
|
||||
$this->css[$relation][$media][$i]['file'] = $source;
|
||||
$this->css[$relation][$media][$i]['content'] = '';
|
||||
$this->css[$relation][$media][$i]['basename'] = '';
|
||||
$this->css[$relation][$media][$i]['title'] = $title;
|
||||
$this->css[$relation][$media][$i]['position-key'] = $i;
|
||||
$this->css[$relation][$media][$i]['original'] = $cssTags[0][$i];
|
||||
|
||||
// styles which are added inside the document must be parsed again to fetch the pure css code
|
||||
$cssTags[1][$i] = (strtolower($cssTags[1][$i]) === 'sty' ? 'style' : strtolower($cssTags[1][$i]));
|
||||
if ($cssTags[1][$i] === 'style') {
|
||||
$cssContent = array();
|
||||
preg_match_all($filterInDocumentPattern, $cssTags[0][$i], $cssContent);
|
||||
|
||||
// we doesn't need to continue if it was an empty style tag
|
||||
if ($cssContent[1][0] === '') {
|
||||
unset($this->css[$relation][$media][$i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// ignore this file if the content could not be fetched
|
||||
$doNotRemoveInDoc = $this->configuration['css.']['doNotRemoveInDoc'] === '1';
|
||||
if ($doNotRemoveInDoc || $ignoreDataFlagSet) {
|
||||
$this->css[$relation][$media][$i]['minify-ignore'] = TRUE;
|
||||
$this->css[$relation][$media][$i]['compress-ignore'] = TRUE;
|
||||
$this->css[$relation][$media][$i]['merge-ignore'] = TRUE;
|
||||
$this->css[$relation][$media][$i]['addInDocument'] = TRUE;
|
||||
$this->css[$relation][$media][$i]['useOriginalCodeLine'] = TRUE;
|
||||
$this->css[$relation][$media][$i]['content'] = $cssContent[1][0];
|
||||
continue;
|
||||
}
|
||||
|
||||
// save the content into a temporary file
|
||||
$hash = md5($cssContent[1][0]);
|
||||
$source = $this->tempDirectories['temp'] . 'inDocument-' . $hash;
|
||||
$tempFile = $source . '.css';
|
||||
if (!file_exists($source . '.css')) {
|
||||
$this->writeFile($tempFile, $cssContent[1][0]);
|
||||
}
|
||||
|
||||
// try to resolve any @import occurrences
|
||||
/** @noinspection PhpUndefinedClassInspection */
|
||||
$content = \Minify_ImportProcessor::process($tempFile);
|
||||
$this->css[$relation][$media][$i]['file'] = $tempFile;
|
||||
$this->css[$relation][$media][$i]['content'] = $content;
|
||||
$this->css[$relation][$media][$i]['basename'] = basename($source);
|
||||
} elseif ($source !== '') {
|
||||
// try to fetch the content of the css file
|
||||
$file = $source;
|
||||
if ($GLOBALS['TSFE']->absRefPrefix !== '' && strpos($file, $GLOBALS['TSFE']->absRefPrefix) === 0) {
|
||||
$file = substr($file, strlen($GLOBALS['TSFE']->absRefPrefix) - 1);
|
||||
}
|
||||
if (file_exists(PATH_site . $file)) {
|
||||
$content = \Minify_ImportProcessor::process(PATH_site . $file);
|
||||
} else {
|
||||
$tempFile = $this->getExternalFile($source);
|
||||
$content = \Minify_ImportProcessor::process($tempFile);
|
||||
}
|
||||
|
||||
// ignore this file if the content could not be fetched
|
||||
if ($content === '' || $ignoreDataFlagSet) {
|
||||
$this->css[$relation][$media][$i]['minify-ignore'] = TRUE;
|
||||
$this->css[$relation][$media][$i]['compress-ignore'] = TRUE;
|
||||
$this->css[$relation][$media][$i]['merge-ignore'] = TRUE;
|
||||
$this->css[$relation][$media][$i]['useOriginalCodeLine'] = TRUE;
|
||||
continue;
|
||||
}
|
||||
|
||||
// check if the file should be ignored for some processes
|
||||
$ignoreAmount = 0;
|
||||
if ($this->configuration['css.']['minify.']['ignore'] !== '') {
|
||||
if (preg_match($this->configuration['css.']['minify.']['ignore'], $source)) {
|
||||
$this->css[$relation][$media][$i]['minify-ignore'] = TRUE;
|
||||
++$ignoreAmount;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->configuration['css.']['compress.']['ignore'] !== '') {
|
||||
if (preg_match($this->configuration['css.']['compress.']['ignore'], $source)) {
|
||||
$this->css[$relation][$media][$i]['compress-ignore'] = TRUE;
|
||||
++$ignoreAmount;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->configuration['css.']['merge.']['ignore'] !== '') {
|
||||
if (preg_match($this->configuration['css.']['merge.']['ignore'], $source)) {
|
||||
$this->css[$relation][$media][$i]['merge-ignore'] = TRUE;
|
||||
++$ignoreAmount;
|
||||
}
|
||||
}
|
||||
|
||||
if ($ignoreAmount === 3) {
|
||||
$this->css[$relation][$media][$i]['useOriginalCodeLine'] = TRUE;
|
||||
}
|
||||
|
||||
// set the css file with it's content
|
||||
$this->css[$relation][$media][$i]['content'] = $content;
|
||||
}
|
||||
|
||||
// get base name for later usage
|
||||
// base name without file prefix and prefixed hash of the content
|
||||
$filename = basename($source);
|
||||
$hash = md5($content);
|
||||
$this->css[$relation][$media][$i]['basename'] =
|
||||
substr($filename, 0, strrpos($filename, '.')) . '-' . $hash;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method minifies a css file. It's based upon the Minify_CSS class
|
||||
* of the project minify.
|
||||
*
|
||||
* @param array $properties properties of an entry (copy-by-reference is used!)
|
||||
* @return string new filename
|
||||
*/
|
||||
protected function minifyFile(&$properties) {
|
||||
// get new filename
|
||||
$newFile = $this->tempDirectories['minified'] .
|
||||
$properties['basename'] . '.min.css';
|
||||
|
||||
// stop further processing if the file already exists
|
||||
if (file_exists($newFile)) {
|
||||
$properties['basename'] .= '.min';
|
||||
$properties['content'] = file_get_contents($newFile);
|
||||
return $newFile;
|
||||
}
|
||||
|
||||
// minify content
|
||||
if (!class_exists('Minify_CSS', FALSE)) {
|
||||
require_once(ExtensionManagementUtility::extPath('scriptmerger') . 'Resources/Minify/CSS.php');
|
||||
}
|
||||
|
||||
/** @noinspection PhpUndefinedClassInspection */
|
||||
$properties['content'] = \Minify_CSS::minify($properties['content']);
|
||||
|
||||
// save content inside the new file
|
||||
$this->writeFile($newFile, $properties['content']);
|
||||
|
||||
// save new part of the base name
|
||||
$properties['basename'] .= '.min';
|
||||
|
||||
return $newFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method compresses a css file.
|
||||
*
|
||||
* @param array $properties properties of an entry (copy-by-reference is used!)
|
||||
* @return string new filename
|
||||
*/
|
||||
protected function compressFile(&$properties) {
|
||||
$newFile = $this->tempDirectories['compressed'] . $properties['basename'] . '.gz.css';
|
||||
if (file_exists($newFile)) {
|
||||
return $newFile;
|
||||
}
|
||||
|
||||
$this->writeFile($newFile, gzencode($properties['content'], 5));
|
||||
|
||||
return $newFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method writes the css back to the document.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function writeToDocument() {
|
||||
$pattern = '';
|
||||
if (trim($this->configuration['css.']['mergedFilePosition']) !== '') {
|
||||
$pattern = '/' . preg_quote($this->configuration['css.']['mergedFilePosition'], '/') . '/i';
|
||||
}
|
||||
|
||||
$contentShouldBeAddedInline = $this->configuration['css.']['addContentInDocument'] === '1';
|
||||
foreach ($this->css as $relation => $cssByRelation) {
|
||||
$cssByRelation = array_reverse($cssByRelation);
|
||||
foreach ($cssByRelation as $media => $cssByMedia) {
|
||||
$cssByMedia = array_reverse($cssByMedia);
|
||||
foreach ($cssByMedia as $cssProperties) {
|
||||
if ($cssProperties['useOriginalCodeLine']) {
|
||||
$content = $cssProperties['original'];
|
||||
} elseif ($cssProperties['addInDocument'] || $contentShouldBeAddedInline) {
|
||||
$content = LF . "\t" .
|
||||
'<style media="' . $media . '" type="text/css">' . LF .
|
||||
"\t" . '/* <![CDATA[ */' . LF .
|
||||
"\t" . $cssProperties['content'] . LF .
|
||||
"\t" . '/* ]]> */' . LF .
|
||||
"\t" . '</style>' . LF;
|
||||
} else {
|
||||
$file = $cssProperties['file'];
|
||||
if (file_exists($file)) {
|
||||
$file = $GLOBALS['TSFE']->absRefPrefix .
|
||||
(PATH_site === '/' ? $file : str_replace(PATH_site, '', $file));
|
||||
}
|
||||
|
||||
$title = (trim($cssProperties['title']) !== '' ?
|
||||
'title="' . $cssProperties['title'] . '"' : '');
|
||||
$content = LF . "\t" . '<link rel="' . $relation . '" type="text/css" ' .
|
||||
'media="' . $media . '" ' . $title . ' href="' . $file . '" />' . LF;
|
||||
}
|
||||
|
||||
if ($pattern === '' || $cssProperties['merge-ignore']) {
|
||||
$GLOBALS['TSFE']->content = str_replace(
|
||||
'###MERGER' . $cssProperties['position-key'] . 'MERGER###',
|
||||
$content,
|
||||
$GLOBALS['TSFE']->content
|
||||
);
|
||||
continue;
|
||||
} else {
|
||||
$GLOBALS['TSFE']->content = preg_replace(
|
||||
$pattern, $content . '\0', $GLOBALS['TSFE']->content, 1
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove any empty markers
|
||||
$pattern = '/###MERGER[0-9]*?MERGER###/is';
|
||||
$GLOBALS['TSFE']->content = preg_replace($pattern, '', $GLOBALS['TSFE']->content);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
490
typo3conf/ext/scriptmerger/Classes/ScriptmergerJavascript.php
Normal file
490
typo3conf/ext/scriptmerger/Classes/ScriptmergerJavascript.php
Normal file
@@ -0,0 +1,490 @@
|
||||
<?php
|
||||
|
||||
namespace SGalinski\Scriptmerger;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) Stefan Galinski <stefan@sgalinski.de>
|
||||
* All rights reserved
|
||||
*
|
||||
* This script is part of the TYPO3 project. The TYPO3 project is
|
||||
* free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The GNU General Public License can be found at
|
||||
* http://www.gnu.org/copyleft/gpl.html.
|
||||
*
|
||||
* This script is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
|
||||
use JShrink\Minifier;
|
||||
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* This class contains the parsing and replacing functionality for javascript files
|
||||
*/
|
||||
class ScriptmergerJavascript extends ScriptmergerBase {
|
||||
/**
|
||||
* holds the javascript code
|
||||
*
|
||||
* Structure:
|
||||
* - $file
|
||||
* |-content => string
|
||||
* |-basename => string (base name of $file without file prefix)
|
||||
* |-minify-ignore => bool
|
||||
* |-merge-ignore => bool
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $javascript = array();
|
||||
|
||||
/**
|
||||
* Controller for the processing of the javascript files.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function process() {
|
||||
// fetch all javascript content
|
||||
$this->getFiles();
|
||||
|
||||
// minify, compress and merging
|
||||
foreach ($this->javascript as $section => $javascriptBySection) {
|
||||
$mergedContent = '';
|
||||
$positionOfMergedFile = NULL;
|
||||
foreach ($javascriptBySection as $index => $javascriptProperties) {
|
||||
$newFile = '';
|
||||
|
||||
// file should be minified
|
||||
if ($this->configuration['javascript.']['minify.']['enable'] === '1' &&
|
||||
!$javascriptProperties['minify-ignore']
|
||||
) {
|
||||
$newFile = $this->minifyFile($javascriptProperties);
|
||||
}
|
||||
|
||||
// file should be merged
|
||||
if ($this->configuration['javascript.']['merge.']['enable'] === '1' &&
|
||||
!$javascriptProperties['merge-ignore']
|
||||
) {
|
||||
if ($positionOfMergedFile === NULL) {
|
||||
$positionOfMergedFile = $javascriptProperties['position-key'];
|
||||
}
|
||||
|
||||
$mergedContent .= $javascriptProperties['content'] . LF;
|
||||
unset($this->javascript[$section][$index]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// file should be compressed instead?
|
||||
if ($this->configuration['javascript.']['compress.']['enable'] === '1' &&
|
||||
function_exists('gzcompress') && !$javascriptProperties['compress-ignore']
|
||||
) {
|
||||
$newFile = $this->compressFile($javascriptProperties);
|
||||
}
|
||||
|
||||
// minification or compression was used
|
||||
if ($newFile !== '') {
|
||||
$this->javascript[$section][$index]['file'] = $newFile;
|
||||
$this->javascript[$section][$index]['content'] =
|
||||
$javascriptProperties['content'];
|
||||
$this->javascript[$section][$index]['basename'] =
|
||||
$javascriptProperties['basename'];
|
||||
}
|
||||
}
|
||||
|
||||
// save merged content inside a new file
|
||||
if ($this->configuration['javascript.']['merge.']['enable'] === '1' && $mergedContent !== '') {
|
||||
// create property array
|
||||
$properties = array(
|
||||
'content' => $mergedContent,
|
||||
'basename' => $section . '-' . md5($mergedContent) . '.merged'
|
||||
);
|
||||
|
||||
// write merged file in any case
|
||||
$newFile = $this->tempDirectories['merged'] . $properties['basename'] . '.js';
|
||||
if (!file_exists($newFile)) {
|
||||
$this->writeFile($newFile, $properties['content']);
|
||||
}
|
||||
|
||||
// file should be compressed
|
||||
if ($this->configuration['javascript.']['compress.']['enable'] === '1' &&
|
||||
function_exists('gzcompress')
|
||||
) {
|
||||
$newFile = $this->compressFile($properties);
|
||||
}
|
||||
|
||||
// add new entry
|
||||
$this->javascript[$section][] = array(
|
||||
'file' => $newFile,
|
||||
'content' => $properties['content'],
|
||||
'basename' => $properties['basename'],
|
||||
'position-key' => $positionOfMergedFile,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// write javascript content back to the document
|
||||
$this->writeToDocument();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method parses the output content and saves any found javascript files or inline code
|
||||
* into the "javascript" class property. The output content is cleaned up of the found results.
|
||||
*
|
||||
* @return array js files
|
||||
*/
|
||||
protected function getFiles() {
|
||||
// init
|
||||
$javascriptTags = array(
|
||||
'head' => array(),
|
||||
'body' => array()
|
||||
);
|
||||
|
||||
// create search pattern
|
||||
$searchScriptsPattern = '/' .
|
||||
'<script' . // This expression includes any script nodes.
|
||||
'(?=.+?(?:src="(.*?)"|>))' . // It fetches the src attribute.
|
||||
'(?=.+?(?:data-ignore="(.*?)"|>))' . // and the data-ignore attribute of the tag.
|
||||
'[^>]*?>' . // Finally we finish the parsing of the opening tag
|
||||
'.*?<\/script>\s*' . // until the closing tag.
|
||||
'/is';
|
||||
|
||||
// filter pattern for the inDoc scripts (fetches the content)
|
||||
$filterInDocumentPattern = '/' .
|
||||
'<script.*?>' . // The expression removes the opening script tag
|
||||
'(?:.*?\/\*<!\[CDATA\[\*\/)?' . // and the optionally prefixed CDATA string.
|
||||
'(?:.*?<!--)?' . // senseless <!-- construct
|
||||
'\s*(.*?)' . // We save the pure js content,
|
||||
'(?:\s*\/\/\s*-->)?' . // senseless <!-- construct
|
||||
'(?:\s*\/\*\]\]>\*\/)?' . // remove the possible closing CDATA string
|
||||
'\s*<\/script>' . // and closing script tag
|
||||
'/is';
|
||||
|
||||
// parse scripts in the head
|
||||
$head = array();
|
||||
preg_match('/<head>.+?<\/head>/is', $GLOBALS['TSFE']->content, $head);
|
||||
$head = $oldHead = $head[0];
|
||||
|
||||
preg_match_all($searchScriptsPattern, $head, $javascriptTags['head']);
|
||||
$amountOfScriptTags = count($javascriptTags['head'][0]);
|
||||
if ($amountOfScriptTags) {
|
||||
$function = create_function('', 'static $i = 0; return \'###MERGER-head\' . $i++ . \'MERGER###\';');
|
||||
$head = preg_replace_callback($searchScriptsPattern, $function, $head, $amountOfScriptTags);
|
||||
$GLOBALS['TSFE']->content = str_replace($oldHead, $head, $GLOBALS['TSFE']->content);
|
||||
}
|
||||
|
||||
// parse scripts in the body
|
||||
if ($this->configuration['javascript.']['parseBody'] === '1') {
|
||||
$body = array();
|
||||
preg_match('/<body.*>.+?<\/body>/is', $GLOBALS['TSFE']->content, $body);
|
||||
$body = $oldBody = $body[0];
|
||||
|
||||
preg_match_all($searchScriptsPattern, $body, $javascriptTags['body']);
|
||||
$amountOfScriptTags = count($javascriptTags['body'][0]);
|
||||
if ($amountOfScriptTags) {
|
||||
$function = create_function('', 'static $i = 0; return \'###MERGER-body\' . $i++ . \'MERGER###\';');
|
||||
$body = preg_replace_callback($searchScriptsPattern, $function, $body, $amountOfScriptTags);
|
||||
$GLOBALS['TSFE']->content = str_replace($oldBody, $body, $GLOBALS['TSFE']->content);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($javascriptTags as $section => $results) {
|
||||
$amountOfResults = count($results[0]);
|
||||
for ($i = 0; $i < $amountOfResults; ++$i) {
|
||||
// get source attribute
|
||||
$source = trim($results[1][$i]);
|
||||
$isSourceFromMainAttribute = FALSE;
|
||||
if ($source !== '') {
|
||||
preg_match('/^<script([^>]*)>/', trim($results[0][$i]), $scriptAttribute);
|
||||
$isSourceFromMainAttribute = (strpos($scriptAttribute[1], $source) !== FALSE);
|
||||
}
|
||||
$ignoreDataFlagSet = intval($results[2][$i]);
|
||||
|
||||
// add basic entry
|
||||
$this->javascript[$section][$i]['minify-ignore'] = FALSE;
|
||||
$this->javascript[$section][$i]['compress-ignore'] = FALSE;
|
||||
$this->javascript[$section][$i]['merge-ignore'] = FALSE;
|
||||
$this->javascript[$section][$i]['addInDocument'] = FALSE;
|
||||
$this->javascript[$section][$i]['useOriginalCodeLine'] = FALSE;
|
||||
$this->javascript[$section][$i]['file'] = $source;
|
||||
$this->javascript[$section][$i]['content'] = '';
|
||||
$this->javascript[$section][$i]['basename'] = '';
|
||||
$this->javascript[$section][$i]['position-key'] = $i;
|
||||
$this->javascript[$section][$i]['original'] = $results[0][$i];
|
||||
|
||||
if ($isSourceFromMainAttribute) {
|
||||
// try to fetch the content of the css file
|
||||
$file = $source;
|
||||
if ($GLOBALS['TSFE']->absRefPrefix !== '' && strpos($file, $GLOBALS['TSFE']->absRefPrefix) === 0) {
|
||||
$file = substr($file, strlen($GLOBALS['TSFE']->absRefPrefix) - 1);
|
||||
}
|
||||
$file = PATH_site . $file;
|
||||
|
||||
if (file_exists($file)) {
|
||||
$content = file_get_contents($file);
|
||||
} else {
|
||||
$content = $this->getExternalFile($source, TRUE);
|
||||
}
|
||||
|
||||
// ignore this file if the content could not be fetched
|
||||
if (trim($content) === '' || $ignoreDataFlagSet) {
|
||||
$this->javascript[$section][$i]['minify-ignore'] = TRUE;
|
||||
$this->javascript[$section][$i]['compress-ignore'] = TRUE;
|
||||
$this->javascript[$section][$i]['merge-ignore'] = TRUE;
|
||||
$this->javascript[$section][$i]['useOriginalCodeLine'] = TRUE;
|
||||
continue;
|
||||
}
|
||||
|
||||
// check if the file should be ignored for some processes
|
||||
$amountOfIgnores = 0;
|
||||
if ($this->configuration['javascript.']['minify.']['ignore'] !== '' &&
|
||||
preg_match($this->configuration['javascript.']['minify.']['ignore'], $source)
|
||||
) {
|
||||
$this->javascript[$section][$i]['minify-ignore'] = TRUE;
|
||||
++$amountOfIgnores;
|
||||
}
|
||||
|
||||
if ($this->configuration['javascript.']['compress.']['ignore'] !== '' &&
|
||||
preg_match($this->configuration['javascript.']['compress.']['ignore'], $source)
|
||||
) {
|
||||
$this->javascript[$section][$i]['compress-ignore'] = TRUE;
|
||||
++$amountOfIgnores;
|
||||
}
|
||||
|
||||
if ($this->configuration['javascript.']['merge.']['ignore'] !== '' &&
|
||||
preg_match($this->configuration['javascript.']['merge.']['ignore'], $source)
|
||||
) {
|
||||
$this->javascript[$section][$i]['merge-ignore'] = TRUE;
|
||||
++$amountOfIgnores;
|
||||
}
|
||||
|
||||
if ($amountOfIgnores === 3) {
|
||||
$this->javascript[$section][$i]['useOriginalCodeLine'] = TRUE;
|
||||
}
|
||||
|
||||
// set the javascript file with it's content
|
||||
$this->javascript[$section][$i]['file'] = $source;
|
||||
$this->javascript[$section][$i]['content'] = $content;
|
||||
|
||||
// get base name for later usage
|
||||
// base name without file prefix and prefixed hash of the content
|
||||
$filename = basename($source);
|
||||
$hash = md5($content);
|
||||
$this->javascript[$section][$i]['basename'] =
|
||||
substr($filename, 0, strrpos($filename, '.')) . '-' . $hash;
|
||||
|
||||
} else {
|
||||
// scripts which are added inside the document must be parsed again
|
||||
// to fetch the pure js code
|
||||
$javascriptContent = array();
|
||||
preg_match_all($filterInDocumentPattern, $results[0][$i], $javascriptContent);
|
||||
|
||||
// we doesn't need to continue if it was an empty script tag
|
||||
if ($javascriptContent[1][0] === '') {
|
||||
unset($this->javascript[$section][$i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$doNotRemoveinDocInBody =
|
||||
($this->configuration['javascript.']['doNotRemoveInDocInBody'] === '1' && $section === 'body');
|
||||
$doNotRemoveinDocInHead =
|
||||
($this->configuration['javascript.']['doNotRemoveInDocInHead'] === '1' && $section === 'head');
|
||||
if ($doNotRemoveinDocInHead || $doNotRemoveinDocInBody || $ignoreDataFlagSet) {
|
||||
$this->javascript[$section][$i]['minify-ignore'] = TRUE;
|
||||
$this->javascript[$section][$i]['compress-ignore'] = TRUE;
|
||||
$this->javascript[$section][$i]['merge-ignore'] = TRUE;
|
||||
$this->javascript[$section][$i]['useOriginalCodeLine'] = TRUE;
|
||||
$this->javascript[$section][$i]['addInDocument'] = TRUE;
|
||||
$this->javascript[$section][$i]['content'] = $javascriptContent[1][0];
|
||||
continue;
|
||||
}
|
||||
|
||||
// save the content into a temporary file
|
||||
$hash = md5($javascriptContent[1][0]);
|
||||
$source = $this->tempDirectories['temp'] . 'inDocument-' . $hash;
|
||||
|
||||
if (!file_exists($source . '.js')) {
|
||||
$this->writeFile($source . '.js', $javascriptContent[1][0]);
|
||||
}
|
||||
|
||||
$this->javascript[$section][$i]['file'] = $source . '.js';
|
||||
$this->javascript[$section][$i]['content'] = $javascriptContent[1][0];
|
||||
$this->javascript[$section][$i]['basename'] = basename($source);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method minifies a javascript file. It's based upon the JSMin+ class
|
||||
* of the project minify. Alternatively the old JSMin class can be used, but it's
|
||||
* definitely not the preferred solution!
|
||||
*
|
||||
* @param array $properties properties of an entry (copy-by-reference is used!)
|
||||
* @return string new filename
|
||||
*/
|
||||
protected function minifyFile(&$properties) {
|
||||
// stop further processing if the file already exists
|
||||
$newFile = $this->tempDirectories['minified'] . $properties['basename'] . '.min.js';
|
||||
if (file_exists($newFile)) {
|
||||
$properties['basename'] .= '.min';
|
||||
$properties['content'] = file_get_contents($newFile);
|
||||
return $newFile;
|
||||
}
|
||||
|
||||
// check for conditional compilation code to fix an issue with jsmin+
|
||||
$hasConditionalCompilation = FALSE;
|
||||
if ($this->configuration['javascript.']['minify.']['useJSMinPlus'] === '1') {
|
||||
$hasConditionalCompilation = preg_match('/\/\*@cc_on/is', $properties['content']);
|
||||
}
|
||||
|
||||
// minify content (the ending semicolon must be added to prevent minimisation bugs)
|
||||
$hasErrors = FALSE;
|
||||
$minifiedContent = '';
|
||||
try {
|
||||
if (!$hasConditionalCompilation && $this->configuration['javascript.']['minify.']['useJShrink'] === '1') {
|
||||
if (!class_exists('JShrink\Minifier', FALSE)) {
|
||||
require_once(ExtensionManagementUtility::extPath('scriptmerger') . 'Resources/JShrink/Minifier.php');
|
||||
}
|
||||
|
||||
$minifiedContent = Minifier::minify($properties['content']);
|
||||
} elseif (!$hasConditionalCompilation && $this->configuration['javascript.']['minify.']['useJSMinPlus'] === '1') {
|
||||
if (!class_exists('JSMinPlus', FALSE)) {
|
||||
require_once(ExtensionManagementUtility::extPath('scriptmerger') . 'Resources/jsminplus.php');
|
||||
}
|
||||
|
||||
$minifiedContent = \JSMinPlus::minify($properties['content']);
|
||||
|
||||
} else {
|
||||
if (!class_exists('JSMin', FALSE)) {
|
||||
require_once(ExtensionManagementUtility::extPath('scriptmerger') . 'Resources/jsmin.php');
|
||||
}
|
||||
|
||||
/** @noinspection PhpUndefinedClassInspection */
|
||||
$minifiedContent = \JSMin::minify($properties['content']);
|
||||
}
|
||||
} catch (\Exception $exception) {
|
||||
$hasErrors = TRUE;
|
||||
}
|
||||
|
||||
// check if the minified content has more than two characters or more than 50 lines and no errors occurred
|
||||
if (!$hasErrors && (strlen($minifiedContent) > 2 || count(explode(LF, $minifiedContent)) > 50)) {
|
||||
$properties['content'] = $minifiedContent . ';';
|
||||
} else {
|
||||
$message = 'This javascript file could not be minified: "' . $properties['file'] . '"! ' .
|
||||
'You should exclude it from the minification process!';
|
||||
GeneralUtility::sysLog($message, 'scriptmerger', GeneralUtility::SYSLOG_SEVERITY_ERROR);
|
||||
}
|
||||
|
||||
$this->writeFile($newFile, $properties['content']);
|
||||
$properties['basename'] .= '.min';
|
||||
|
||||
return $newFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method compresses a javascript file.
|
||||
*
|
||||
* @param array $properties properties of an entry (copy-by-reference is used!)
|
||||
* @return string new filename
|
||||
*/
|
||||
protected function compressFile(&$properties) {
|
||||
$newFile = $this->tempDirectories['compressed'] . $properties['basename'] . '.gz.js';
|
||||
if (file_exists($newFile)) {
|
||||
return $newFile;
|
||||
}
|
||||
|
||||
$this->writeFile($newFile, gzencode($properties['content'], 5));
|
||||
|
||||
return $newFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method writes the javascript back to the document.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function writeToDocument() {
|
||||
$shouldBeAddedInDoc = $this->configuration['javascript.']['addContentInDocument'] === '1';
|
||||
foreach ($this->javascript as $section => $javascriptBySection) {
|
||||
ksort($javascriptBySection);
|
||||
if (!is_array($javascriptBySection)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// addBeforeBody was deprecated in version 4.0.0 and can be removed later on
|
||||
$pattern = '';
|
||||
if ($section === 'body' || $this->configuration['javascript.']['addBeforeBody'] === '1') {
|
||||
$pattern = '/' . preg_quote($this->configuration['javascript.']['mergedBodyFilePosition'], '/') . '/i';
|
||||
} elseif (trim($this->configuration['javascript.']['mergedHeadFilePosition']) !== '') {
|
||||
$pattern = '/' . preg_quote($this->configuration['javascript.']['mergedHeadFilePosition'], '/') . '/i';
|
||||
}
|
||||
|
||||
foreach ($javascriptBySection as $javascriptProperties) {
|
||||
if ($javascriptProperties['useOriginalCodeLine']) {
|
||||
$content = $javascriptProperties['original'];
|
||||
} elseif ($javascriptProperties['addInDocument'] || $shouldBeAddedInDoc) {
|
||||
$content = "\t" .
|
||||
'<script type="text/javascript">' . LF .
|
||||
"\t" . '/* <![CDATA[ */' . LF .
|
||||
"\t" . $javascriptProperties['content'] . LF .
|
||||
"\t" . '/* ]]> */' . LF .
|
||||
"\t" . '</script>' . LF;
|
||||
} else {
|
||||
$file = $javascriptProperties['file'];
|
||||
if (file_exists($file)) {
|
||||
$file = $GLOBALS['TSFE']->absRefPrefix .
|
||||
(PATH_site === '/' ? $file : str_replace(PATH_site, '', $file));
|
||||
}
|
||||
|
||||
if ($this->configuration['javascript.']['deferLoading'] === '1') {
|
||||
$content = '
|
||||
<script type="text/javascript" defer="defer">
|
||||
function downloadJSAtOnload() {
|
||||
var element = document.createElement("script");
|
||||
element.src = "' . $file . '";
|
||||
document.body.appendChild(element);
|
||||
}
|
||||
|
||||
if (window.addEventListener) {
|
||||
window.addEventListener("load", downloadJSAtOnload, false);
|
||||
} else if (window.attachEvent) {
|
||||
window.attachEvent("onload", downloadJSAtOnload);
|
||||
} else {
|
||||
window.onload = downloadJSAtOnload;
|
||||
}
|
||||
</script>';
|
||||
} else {
|
||||
$content = "\t" .
|
||||
'<script type="text/javascript" src="' . $file . '"></script>' . LF;
|
||||
}
|
||||
}
|
||||
|
||||
if ($pattern === '' || $javascriptProperties['merge-ignore']) {
|
||||
// add body scripts back to their original place if they were ignored
|
||||
$GLOBALS['TSFE']->content = str_replace(
|
||||
'###MERGER-' . $section . $javascriptProperties['position-key'] . 'MERGER###',
|
||||
$content,
|
||||
$GLOBALS['TSFE']->content
|
||||
);
|
||||
} else {
|
||||
$GLOBALS['TSFE']->content = preg_replace($pattern, $content . '\0', $GLOBALS['TSFE']->content, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove any empty markers
|
||||
$pattern = '/###MERGER-(head|body)[0-9]*?MERGER###/is';
|
||||
$GLOBALS['TSFE']->content = preg_replace($pattern, '', $GLOBALS['TSFE']->content);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace SGalinski\Scriptmerger;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) Stefan Galinski (stefan@sgalinski.de)
|
||||
* All rights reserved
|
||||
*
|
||||
* This script is part of the TYPO3 project. The TYPO3 project is
|
||||
* free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The GNU General Public License can be found at
|
||||
* http://www.gnu.org/copyleft/gpl.html.
|
||||
* A copy is found in the textfile GPL.txt and important notices to the license
|
||||
* from the author is found in LICENSE.txt distributed with these scripts.
|
||||
*
|
||||
*
|
||||
* This script is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
|
||||
/**
|
||||
* This class contains a hook method for the "clear all cache" action in the TYPO3 backend.
|
||||
*/
|
||||
class user_ScriptmergerCacheHook {
|
||||
/**
|
||||
* Contains the temporary directories of this extension.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $tempDirectories = array();
|
||||
|
||||
/**
|
||||
* Initializes some class variables...
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->tempDirectories = array(
|
||||
PATH_site . 'typo3temp/scriptmerger/' => 0,
|
||||
PATH_site . 'typo3temp/scriptmerger/temp/' => 0,
|
||||
PATH_site . 'typo3temp/scriptmerger/uncompressed/' => 1209600,
|
||||
PATH_site . 'typo3temp/scriptmerger/compressed/' => 1209600
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear cache post processor
|
||||
*
|
||||
* This method deletes all temporary files that are older than one month and
|
||||
* if the deletion of the whole cache is requested.
|
||||
*
|
||||
* @param array $params
|
||||
* @return void
|
||||
*/
|
||||
public function clearCachePostProc(&$params) {
|
||||
if ($params['cacheCmd'] !== 'all') {
|
||||
return;
|
||||
}
|
||||
|
||||
$now = $GLOBALS['EXEC_TIME'];
|
||||
foreach ($this->tempDirectories as $tempDirectory => $maxAge) {
|
||||
if (!is_dir($tempDirectory)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$handle = opendir($tempDirectory);
|
||||
while (FALSE !== ($file = readdir($handle))) {
|
||||
if ($file === '.' || $file === '..') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_file($tempDirectory . $file)) {
|
||||
// get last modification time
|
||||
$lastAccess = fileatime($tempDirectory . $file);
|
||||
$age = $now - $lastAccess;
|
||||
|
||||
if ($age >= $maxAge) {
|
||||
unlink($tempDirectory . $file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,222 @@
|
||||
<?php
|
||||
|
||||
namespace SGalinski\Scriptmerger;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) Stefan Galinski <stefan@sgalinski.de>
|
||||
* All rights reserved
|
||||
*
|
||||
* This script is part of the TYPO3 project. The TYPO3 project is
|
||||
* free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The GNU General Public License can be found at
|
||||
* http://www.gnu.org/copyleft/gpl.html.
|
||||
*
|
||||
* This script is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
|
||||
|
||||
/**
|
||||
* This class contains the output hooks that trigger the scriptmerger process
|
||||
*/
|
||||
class user_ScriptmergerOutputHook {
|
||||
/**
|
||||
* holds the extension configuration
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $extensionConfiguration = array();
|
||||
|
||||
/**
|
||||
* This method fetches and prepares the extension configuration.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function prepareExtensionConfiguration() {
|
||||
$this->extensionConfiguration = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['scriptmerger']);
|
||||
|
||||
$tsSetup = $GLOBALS['TSFE']->tmpl->setup['plugin.']['tx_scriptmerger.'];
|
||||
if (is_array($tsSetup)) {
|
||||
foreach ($tsSetup as $key => $value) {
|
||||
$this->extensionConfiguration[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
// no compression allowed if content should be added inside the document
|
||||
if ($this->extensionConfiguration['css.']['addContentInDocument'] === '1') {
|
||||
$this->extensionConfiguration['css.']['compress.']['enable'] = '0';
|
||||
}
|
||||
|
||||
if ($this->extensionConfiguration['javascript.']['addContentInDocument'] === '1') {
|
||||
$this->extensionConfiguration['javascript.']['compress.']['enable'] = '0';
|
||||
}
|
||||
|
||||
// prepare ignore expressions
|
||||
if ($this->extensionConfiguration['css.']['minify.']['ignore'] !== '') {
|
||||
$this->extensionConfiguration['css.']['minify.']['ignore'] = '/.*(' .
|
||||
str_replace(',', '|', $this->extensionConfiguration['css.']['minify.']['ignore']) .
|
||||
').*/isU';
|
||||
}
|
||||
|
||||
if ($this->extensionConfiguration['css.']['compress.']['ignore'] !== '') {
|
||||
$this->extensionConfiguration['css.']['compress.']['ignore'] = '/.*(' .
|
||||
str_replace(',', '|', $this->extensionConfiguration['css.']['compress.']['ignore']) .
|
||||
').*/isU';
|
||||
}
|
||||
|
||||
if ($this->extensionConfiguration['css.']['merge.']['ignore'] !== '') {
|
||||
$this->extensionConfiguration['css.']['merge.']['ignore'] = '/.*(' .
|
||||
str_replace(',', '|', $this->extensionConfiguration['css.']['merge.']['ignore']) .
|
||||
').*/isU';
|
||||
}
|
||||
|
||||
if ($this->extensionConfiguration['javascript.']['minify.']['ignore'] !== '') {
|
||||
$this->extensionConfiguration['javascript.']['minify.']['ignore'] = '/.*(' .
|
||||
str_replace(',', '|', $this->extensionConfiguration['javascript.']['minify.']['ignore']) .
|
||||
').*/isU';
|
||||
}
|
||||
|
||||
if ($this->extensionConfiguration['javascript.']['compress.']['ignore'] !== '') {
|
||||
$this->extensionConfiguration['javascript.']['compress.']['ignore'] = '/.*(' .
|
||||
str_replace(',', '|', $this->extensionConfiguration['javascript.']['compress.']['ignore']) .
|
||||
').*/isU';
|
||||
}
|
||||
|
||||
if ($this->extensionConfiguration['javascript.']['merge.']['ignore'] !== '') {
|
||||
$this->extensionConfiguration['javascript.']['merge.']['ignore'] = '/.*(' .
|
||||
str_replace(',', '|', $this->extensionConfiguration['javascript.']['merge.']['ignore']) .
|
||||
').*/isU';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This hook is executed if the page contains *_INT objects! It's called always as the
|
||||
* last hook before the final output. This isn't the case if you are using a
|
||||
* static file cache like nc_staticfilecache.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function contentPostProcOutput() {
|
||||
/** @var $tsfe TypoScriptFrontendController */
|
||||
$tsfe = $GLOBALS['TSFE'];
|
||||
if (!$tsfe->isINTincScript() || intval(GeneralUtility::_GP('disableScriptmerger')) === 1) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
$this->prepareExtensionConfiguration();
|
||||
$this->process();
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* The hook is only executed if the page does not contains any *_INT objects. It's called
|
||||
* always if the page was not already cached or on first hit!
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function contentPostProcAll() {
|
||||
/** @var $tsfe TypoScriptFrontendController */
|
||||
$tsfe = $GLOBALS['TSFE'];
|
||||
if ($tsfe->isINTincScript() || intval(GeneralUtility::_GP('disableScriptmerger')) === 1) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
$this->prepareExtensionConfiguration();
|
||||
$this->process();
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains the process logic of the whole plugin!
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function process() {
|
||||
$javascriptEnabled = $this->extensionConfiguration['javascript.']['enable'] === '1';
|
||||
$cssEnabled = $this->extensionConfiguration['css.']['enable'] === '1';
|
||||
if ($cssEnabled || $javascriptEnabled) {
|
||||
/** @var ScriptmergerConditionalCommentPreserver $conditionalCommentPreserver */
|
||||
$conditionalCommentPreserver = GeneralUtility::makeInstance(
|
||||
'SGalinski\Scriptmerger\ScriptmergerConditionalCommentPreserver'
|
||||
);
|
||||
$conditionalCommentPreserver->read();
|
||||
|
||||
if ($cssEnabled) {
|
||||
/** @var ScriptmergerCss $cssProcessor */
|
||||
$cssProcessor = GeneralUtility::makeInstance('SGalinski\Scriptmerger\ScriptmergerCss');
|
||||
$cssProcessor->injectExtensionConfiguration($this->extensionConfiguration);
|
||||
$cssProcessor->process();
|
||||
}
|
||||
|
||||
if ($javascriptEnabled) {
|
||||
/** @var ScriptmergerJavascript $javascriptProcessor */
|
||||
$javascriptProcessor = GeneralUtility::makeInstance('SGalinski\Scriptmerger\ScriptmergerJavascript');
|
||||
$javascriptProcessor->injectExtensionConfiguration($this->extensionConfiguration);
|
||||
$javascriptProcessor->process();
|
||||
}
|
||||
|
||||
$conditionalCommentPreserver->writeBack();
|
||||
}
|
||||
|
||||
if (is_array($this->extensionConfiguration['urlRegularExpressions.']) &&
|
||||
count($this->extensionConfiguration['urlRegularExpressions.'])
|
||||
) {
|
||||
$this->executeUserDefinedRegularExpressionsOnContent(
|
||||
$this->extensionConfiguration['urlRegularExpressions.']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes user defined regular expressions on the href/src urls for e.g. use an cookie-less asset domain.
|
||||
*
|
||||
* @param array $expressions
|
||||
* @return void
|
||||
*/
|
||||
protected function executeUserDefinedRegularExpressionsOnContent($expressions) {
|
||||
foreach ($expressions as $index => $expression) {
|
||||
if (strpos($index, '.') !== FALSE || !isset($expressions[$index . '.']['replacement'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$replacement = trim($expressions[$index . '.']['replacement']);
|
||||
if ($replacement === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($expressions[$index . '.']['useWholeContent'] === '1') {
|
||||
$GLOBALS['TSFE']->content = preg_replace($expression, $replacement, $GLOBALS['TSFE']->content);
|
||||
} else {
|
||||
$userExpression = trim(str_replace('/', '\/', $expression));
|
||||
$expression = '/<(?:img|link|style|script|meta|input)' .
|
||||
'(?=[^>]+?(?:content|href|src)="(' . $userExpression . ')")[^>]*?>/iU';
|
||||
preg_match_all($expression, $GLOBALS['TSFE']->content, $matches);
|
||||
if (is_array($matches[1])) {
|
||||
foreach ($matches[1] as $match) {
|
||||
if (trim($match) === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$changedUrl = preg_replace('/' . $userExpression . '/is', $replacement, $match);
|
||||
$GLOBALS['TSFE']->content = str_replace($match, $changedUrl, $GLOBALS['TSFE']->content);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
Reference in New Issue
Block a user