/** * Method to parse all link to css files from the html markup * and compress it * * @param string $htmlMarkup HTML Content to response to browser * * @return void */ public static function compress($scripts) { static $compressedFiles; // Get object for working with URI $uri = JUri::getInstance(); // Generate link prefix if current scheme is HTTPS $prefix = ''; if ($uri->getScheme() == 'https') { $prefix = $uri->toString(array('scheme', 'host', 'port')); } // Initialize variables $groupIndex = 0; $groupType = 'default'; $groupFiles = array(); $compress = array(); // Sometime, script file need to be stored in the original location and file name $document = JFactory::getDocument(); $leaveAlone = preg_split('/[\\r\\n]+/', $document->params->get('compressionExclude')); // We already know some files must be excluded from compression $leaveAlone[] = 'modal.js'; $leaveAlone[] = 'tiny_mce.js'; $leaveAlone[] = 'tinymce.min.js'; // Parse script tags foreach ($scripts as $key => $line) { // Set default group $attributes['group'] = 'default'; $attributes['src'] = $key; // Add to result list if this is external file if (!($isInternal = JSNTplCompressHelper::isInternal($attributes['src'])) or strpos($attributes['src'], '//') === 0) { // Add collected files to compress list if (!empty($groupFiles)) { $compress[] = array('files' => $groupFiles[$groupIndex], 'group' => $groupType); $groupFiles = array(); } $compress[] = array('src' => $attributes['src']); continue; } // Add to result list if this is dynamic generation content $questionPos = false; if (($questionPos = strpos($attributes['src'], '?')) !== false) { $isDynamic = substr($attributes['src'], $questionPos - 4, 4) == '.php'; $path = JSNTplCompressHelper::getFilePath(substr($attributes['src'], 0, $questionPos)); // Check if this is a dynamic generation content if (!$isDynamic and JSNTplCompressHelper::isInternal($attributes['src'])) { $isDynamic = !is_file($path); } if ($isDynamic) { // Add collected files to compress list if (!empty($groupFiles)) { $compress[] = array('files' => $groupFiles[$groupIndex], 'group' => $groupType); $groupFiles = array(); } $compress[] = array('src' => $attributes['src']); continue; } } // Check if reserving script file name is required $scriptName = basename($questionPos !== false ? $path : $attributes['src']); if (in_array($scriptName, $leaveAlone)) { $attributes['group'] = 'reserve|' . $scriptName; } // Create new compression group if reserving script file name is required if ($attributes['group'] != $groupType) { // Add collected files to compress list if (isset($groupFiles[$groupIndex]) and !empty($groupFiles[$groupIndex])) { $compress[] = array('files' => $groupFiles[$groupIndex], 'group' => $groupType); } // Increase index number of the group $groupIndex++; $groupType = $attributes['group']; } // Initial group if (!isset($groupFiles[$groupIndex])) { $groupFiles[$groupIndex] = array(); } $src = $attributes['src']; $queryStringIndex = strpos($src, '?'); if ($queryStringIndex !== false) { $src = substr($src, 0, $queryStringIndex); } // Add file to the group $groupFiles[$groupIndex][] = preg_match('/^reserve\\|(.+)$/', $groupType) ? $attributes['src'] : $src; } // Add collected files to result list if (isset($groupFiles[$groupIndex]) and !empty($groupFiles[$groupIndex])) { $compress[] = array('files' => $groupFiles[$groupIndex], 'group' => $groupType); } // Initial compress result $compressResult = array(); $fileCompressed = array(); // Get template details $templateName = JFactory::getApplication()->getTemplate(); // Generate path to store compressed files if (!preg_match('#^(/|\\|[a-z]:)#i', $document->params->get('cacheDirectory'))) { $compressPath = JPATH_ROOT . '/' . rtrim($document->params->get('cacheDirectory'), '\\/'); } else { $compressPath = rtrim($document->params->get('cacheDirectory'), '\\/'); } $compressPath = $compressPath . '/' . $templateName . '/'; // Create directory if not exists if (!is_dir($compressPath)) { JFolder::create($compressPath); } // Loop to each compress element to compress file $modifiedFlag = false; foreach ($compress as $group) { // Ignore compress when group is a external file if (isset($group['src'])) { $compressResult[] = sprintf('<script src="%s" type="text/javascript"></script>', $group['src']); $fileCompressed[] = $group['src']; continue; } // Check if reserving script file name is required if (isset($group['group']) and preg_match('/^reserve\\|(.+)$/', $group['group'])) { $compressResult[] = sprintf('<script src="%s" type="text/javascript"></script>', $group['files'][0]); $fileCompressed[] = $group['files'][0]; continue; } // Generate compress file name $compressFile = md5(implode('', $group['files'])) . '.js'; $lastModified = 0; $splittedFiles = array(); // Check last modified time for each file in the group foreach ($group['files'] as $file) { $path = JSNTplCompressHelper::getFilePath($file); $lastModified = is_file($path) && @filemtime($path) > $lastModified ? @filemtime($path) : $lastModified; } if (@filemtime($compressPath . $compressFile) < $lastModified) { $modifiedFlag = true; } // Compress group when expired if (!is_file($compressPath . $compressFile) or @filemtime($compressPath . $compressFile) < $lastModified) { // Preset compression buffer $buffer = ''; // Preset some variables to hold compression status $processedFiles = array(); $maxFileSize = 1024 * (int) $document->params->get('maxCompressionSize'); $currentSize = 0; // Read content of each file and write it to the cache file foreach ($group['files'] as $file) { $filePath = JSNTplCompressHelper::getFilePath($file); // Skip when cannot access to file if (!is_file($filePath) or !is_readable($filePath)) { continue; } // Prepend path to source file $source = ($currentSize == 0 ? '' : "\n\n") . '/* FILE: ' . str_replace(str_replace('\\', '/', JPATH_ROOT), '', str_replace('\\', '/', $filePath)) . ' */' . "\n" . JFile::read($filePath); // Get length of processed content $length = strlen($source); if ($length > $maxFileSize or $currentSize + $length > $maxFileSize) { // Write buffer to cache file JFile::write($compressPath . $compressFile, $buffer); // Rename created cache file if ($currentSize > 0) { $newFileName = md5(implode('', $processedFiles)) . '.js'; JFile::move($compressPath . $compressFile, $compressPath . $newFileName); // Save every compressed file associated with this page for maintenance later $compressedFiles[] = str_replace('\\', '/', $compressPath) . $newFileName; // Store splitted file URL for later reference $splittedFiles[] = $prefix . str_replace(str_replace('\\', '/', JPATH_ROOT), JUri::root(true), str_replace('\\', '/', $compressPath)) . $newFileName; } // Reset compression buffer $buffer = ''; // Reset current file size $currentSize = $length; $processedFiles = array($filePath); } else { // Update current file size $currentSize += $length; $processedFiles[] = $filePath; } // Append processed content to buffer $buffer .= $source . ";\n"; } // Write buffer to cache file JFile::write($compressPath . $compressFile, $buffer); // Save every compressed file associated with this page for maintenance later $compressedFiles[] = str_replace('\\', '/', $compressPath) . $compressFile; // Prepend splitted compress files into trackable compress file if (count($splittedFiles)) { for ($n = count($splittedFiles), $i = $n - 1; $i >= 0; $i--) { JSNTplCompressHelper::prependIntoFile("// Include: {$splittedFiles[$i]}" . ($i + 1 < $n ? "\n" : "\n\n"), $compressPath . $compressFile); } } } else { // Read compressed file for list of splitted file $include = JFile::read($compressPath . $compressFile); $include = substr($include, 0, strpos($include, "\n\n")); // Parse splitted compress file foreach (explode("\n", $include) as $line) { if (strpos($line, '// Include: ') === 0) { $splittedFiles[] = str_replace('// Include: ', '', $line); } } } // Load splitted compress file if (count($splittedFiles)) { foreach ($splittedFiles as $file) { $compressResult[] = sprintf('<script src="%s" type="text/javascript"></script>', $file); $fileCompressed[] = $file; } } // Add compressed file to the compress result list $compressUrl = str_replace(str_replace('\\', '/', JPATH_ROOT), JUri::root(true), str_replace('\\', '/', $compressPath)) . $compressFile; $compressResult[] = sprintf('<script src="%s" type="text/javascript"></script>', $prefix . $compressUrl); $fileCompressed[] = $prefix . $compressUrl; } // Verify if stylesheets associated with this page has been changed if (isset($compressedFiles)) { $trackFile = $compressPath . 'tracking.php'; $pageLink = JUri::current(); $cleanUp = array(); if (file_exists($trackFile)) { if (!file_exists("{$trackFile}.lock")) { // Get tracking data include $trackFile; if (isset($tracking) && isset($tracking[$pageLink]) && isset($tracking[$pageLink]['js'])) { foreach ($tracking[$pageLink]['js'] as $file) { if (!in_array($file, $compressedFiles)) { // Store obsolete file to be removed $cleanUp[] = $file; } } // Remove obsolete file only if not used in another page foreach ($cleanUp as $file) { $removable = true; foreach ($tracking as $link => $assets) { if ($pageLink == $link) { continue; } if (in_array($file, $assets['js'])) { $removable = false; break; } } if ($removable && !$modifiedFlag) { JFile::delete($file); } } } } } else { // Clean all unmaintained compressed files if ($files = glob($compressPath . '*.js')) { foreach ($files as $file) { $file = str_replace('\\', '/', $file); if (!in_array($file, $compressedFiles)) { JFile::delete($file); } } } } // Update tracking file if not locked if (!file_exists("{$trackFile}.lock")) { // Create lock file $content = 'Updating'; JFile::write("{$trackFile}.lock", $content); // Preset tracking array if (!isset($tracking)) { $tracking = array($pageLink => array()); } $tracking[$pageLink]['js'] = $compressedFiles; // Update tracking data $content = "<?php\n\$tracking = json_decode('" . json_encode($tracking) . "', true);\n?>"; // Update tracking file JFile::write($trackFile, $content); // Remove lock file JFile::delete("{$trackFile}.lock"); } } return $fileCompressed; }
/** * Method to parse all link to css files from the html markup * and compress it * * @param string $htmlMarkup HTML Content to response to browser * * @return void */ public static function compress($styleSheets) { static $compressedFiles; // Get object for working with URI $uri = JUri::getInstance(); // Generate link prefix if current scheme is HTTPS $prefix = ''; if ($uri->getScheme() == 'https') { $prefix = $uri->toString(array('scheme', 'host', 'port')); } // Initialize variables $groupIndex = 0; $groupType = 'screen'; $groupFiles = array(); $compress = array(); // Sometime, stylesheet file need to be stored in the original location and file name $document = JFactory::getDocument(); $leaveAlone = preg_split('/[\\r\\n]+/', $document->params->get('compressionExclude')); // We already know that the file galleria.classic.css must be excluded $leaveAlone[] = 'galleria.classic.css'; // Parse link tags foreach ($styleSheets as $key => $line) { // Set default media attribute $attributes['media'] = is_null($line['media']) ? '' : strtolower($line['media']); $attributes['href'] = $key; // Add to result list if this is external file if (!($isInternal = JSNTplCompressHelper::isInternal($attributes['href'])) or strpos($attributes['href'], '//') === 0) { $compress[] = array('href' => $attributes['href'], 'media' => $attributes['media']); continue; } // Add to result list if this is dynamic generation content $questionPos = false; if (($questionPos = strpos($attributes['href'], '?')) !== false) { $isDynamic = substr($attributes['href'], $questionPos - 4, 4) == '.php'; $path = JSNTplCompressHelper::getFilePath(substr($attributes['href'], 0, $questionPos)); // Check if this is a dynamic generation content if (!$isDynamic and $isInternal) { $isDynamic = !is_file($path); } if ($isDynamic) { $compress[] = array('href' => $attributes['href'], 'media' => $attributes['media']); continue; } } // Check if reserving stylesheet file name is required $stylesheetName = basename($questionPos !== false ? $path : $attributes['href']); if (in_array($stylesheetName, $leaveAlone)) { $attributes['media'] .= '|reserve|' . $stylesheetName; } // Create new compression group when media attribute different with group type if ($attributes['media'] != $groupType) { // Add collected files to compress list if (isset($groupFiles[$groupIndex]) and !empty($groupFiles[$groupIndex])) { $compress[] = array('files' => $groupFiles[$groupIndex], 'media' => $groupType); } // Increase index number of the group $groupIndex++; $groupType = $attributes['media']; } // Initial group if (!isset($groupFiles[$groupIndex])) { $groupFiles[$groupIndex] = array(); } $href = $attributes['href']; $queryStringIndex = strpos($href, '?'); if ($queryStringIndex !== false) { $href = substr($href, 0, $queryStringIndex); } // Add file to the group $groupFiles[$groupIndex][] = preg_match('/^([^\\|]*)\\|reserve\\|.+$/', $groupType) ? $attributes['href'] : $href; } // Add collected files to result list if (isset($groupFiles[$groupIndex]) and !empty($groupFiles[$groupIndex])) { $compress[] = array('files' => $groupFiles[$groupIndex], 'media' => $groupType); } // Initial compress result $compressResult = array(); $fileCompressed = array(); // Get template details $templateName = JFactory::getApplication()->getTemplate(); // Generate path to cache directory if (!preg_match('#^(/|\\|[a-z]:)#i', $document->params->get('cacheDirectory'))) { $compressPath = JPATH_ROOT . '/' . rtrim($document->params->get('cacheDirectory'), '\\/'); } else { $compressPath = rtrim($document->params->get('cacheDirectory'), '\\/'); } $compressPath = $compressPath . '/' . $templateName . '/'; // Create directory if not exists if (!is_dir($compressPath)) { JFolder::create($compressPath); } // Loop to each compress element to compress file $modifiedFlag = false; foreach ($compress as $group) { // Ignore compress when group is a external file if (isset($group['href'])) { $ignoreCompressMedia = ''; $link = '<link rel="stylesheet" href="' . $group['href'] . '" '; if (isset($group['media']) and !empty($group['media'])) { $link .= 'media="' . $group['media'] . '" '; $ignoreCompressMedia = $group['media']; } $link .= '/>'; $compressResult[] = $link; $fileCompressed[] = array('media' => $ignoreCompressMedia, 'file' => $group['href']); continue; } // Check if reserving stylesheet file name is required if (isset($group['media']) and preg_match('/^([^\\|]*)\\|reserve\\|.+$/', $group['media'], $m)) { $reservingStylesshetMedia = ''; $link = '<link rel="stylesheet" href="' . $group['files'][0] . '" '; if (isset($m[1]) and !empty($m[1])) { $link .= 'media="' . $m[1] . '" '; $reservingStylesshetMedia = $m[1]; } $link .= '/>'; $compressResult[] = $link; $fileCompressed[] = array('media' => $reservingStylesshetMedia, 'file' => $group['files'][0]); continue; } // Generate compress file name $compressFile = md5(implode('', $group['files'])) . '.css'; $lastModified = 0; // Check last modified time for each file in the group foreach ($group['files'] as $file) { $path = JSNTplCompressHelper::getFilePath($file); $lastModified = is_file($path) && @filemtime($path) > $lastModified ? @filemtime($path) : $lastModified; } if (@filemtime($compressPath . $compressFile) < $lastModified) { $modifiedFlag = true; } // Compress group when expired if (!is_file($compressPath . $compressFile) or @filemtime($compressPath . $compressFile) < $lastModified) { // Preset compression buffer $buffer = ''; // Preset remote file array $remoteFiles = array(); // Preset some variables to hold compression status $processedFiles = array(); $maxFileSize = 1024 * (int) $document->params->get('maxCompressionSize'); $currentSize = 0; // Read content of each file and write it to the cache file foreach ($group['files'] as $file) { $filePath = JSNTplCompressHelper::getFilePath($file); // Skip when cannot access to file if (!is_file($filePath) or !is_readable($filePath)) { continue; } // Do compression $result = trim(self::_loadFileInto($buffer, $filePath, $maxFileSize, $currentSize, $remoteFiles)); if (empty($result)) { // Store processed file $processedFiles[] = $filePath; } else { // Write buffer to cache file JFile::write($compressPath . $compressFile, $buffer); // Rename created cache file $newFileName = md5(implode('', $processedFiles)) . '.css'; JFile::move($compressPath . $compressFile, $compressPath . $newFileName); // Save every compressed file associated with this page for maintenance later $compressedFiles[] = str_replace('\\', '/', $compressPath) . $newFileName; // Add compressed file to the remote file import list $remoteFiles[] = str_replace(str_replace('\\', '/', JPATH_ROOT), JUri::root(true), str_replace('\\', '/', $compressPath)) . $newFileName; // Reset compression buffer $buffer = $result; // Reset compression status variables $currentSize = strlen($result); $processedFiles = array($filePath); } } // Write buffer to cache file JFile::write($compressPath . $compressFile, $buffer); // Save every compressed file associated with this page for maintenance later $compressedFiles[] = str_replace('\\', '/', $compressPath) . $compressFile; if (!empty($remoteFiles)) { for ($n = count($remoteFiles), $i = $n - 1; $i >= 0; $i--) { JSNTplCompressHelper::prependIntoFile("@import url({$remoteFiles[$i]});" . ($i + 1 < $n ? "\n" : "\n\n"), $compressPath . $compressFile); } } } // Add compressed file to the compress result list $compressUrl = str_replace(str_replace('\\', '/', JPATH_ROOT), JUri::root(true), str_replace('\\', '/', $compressPath)) . $compressFile; $link = '<link rel="stylesheet" href="' . $prefix . $compressUrl . '" '; $mediaCompressedFile = ''; if (isset($group['media']) and !empty($group['media'])) { $link .= 'media="' . preg_replace('/\\|reserve\\|(.+)$/', '', $group['media']) . '" '; $mediaCompressedFile = preg_replace('/\\|reserve\\|(.+)$/', '', $group['media']); } $link .= '/>'; $compressResult[] = $link; $fileCompressed[] = array('media' => $mediaCompressedFile, 'file' => $compressUrl); } // Verify if stylesheets associated with this page has been changed if (isset($compressedFiles)) { $trackFile = $compressPath . 'tracking.php'; $pageLink = JUri::current(); $cleanUp = array(); if (file_exists($trackFile)) { if (!file_exists("{$trackFile}.lock")) { // Get tracking data include $trackFile; if (isset($tracking) && isset($tracking[$pageLink]) && isset($tracking[$pageLink]['css'])) { foreach ($tracking[$pageLink]['css'] as $file) { if (!in_array($file, $compressedFiles)) { // Store obsolete file to be removed $cleanUp[] = $file; } } // Remove obsolete file only if not used in another page foreach ($cleanUp as $file) { $removable = true; foreach ($tracking as $link => $assets) { if ($pageLink == $link) { continue; } if (@in_array($file, $assets['css'])) { $removable = false; break; } } if ($removable && !$modifiedFlag) { JFile::delete($file); } } } } } else { // Clean all unmaintained compressed files if ($files = glob($compressPath . '*.css')) { foreach ($files as $file) { $file = str_replace('\\', '/', $file); if (!in_array($file, $compressedFiles)) { JFile::delete($file); } } } } // Update tracking file if not locked if (!file_exists("{$trackFile}.lock")) { // Create lock file $content = 'Updating'; JFile::write("{$trackFile}.lock", $content); // Preset tracking array if (!isset($tracking)) { $tracking = array($pageLink => array()); } $tracking[$pageLink]['css'] = $compressedFiles; // Update tracking data $content = "<?php\n\$tracking = json_decode('" . json_encode($tracking) . "', true);\n?>"; // Update tracking file JFile::write($trackFile, $content); // Remove lock file JFile::delete("{$trackFile}.lock"); } } return $fileCompressed; }