Skip to content

Instantly share code, notes, and snippets.

@colinmollenhour
Created April 12, 2012 03:44
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save colinmollenhour/2364485 to your computer and use it in GitHub Desktop.
Save colinmollenhour/2364485 to your computer and use it in GitHub Desktop.
Magento merged CS/JSS improvements
<?php
/**
* Magento
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://opensource.org/licenses/osl-3.0.php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@magentocommerce.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade Magento to newer
* versions in the future. If you wish to customize Magento for your
* needs please refer to http://www.magentocommerce.com for more information.
*
* @category Mage
* @package Mage_Core
* @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com)
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
*/
class Template_Core_Model_Design_Package extends Mage_Core_Model_Design_Package
{
const CACHE_KEY = 'design_merged';
const CACHE_TAG = 'design_merged';
protected $_locked = FALSE;
protected $_baseMergedUrl;
/**
* Merge specified css files and return URL to the merged file on success
*
* @param $files
* @return string
*/
public function getMergedCssUrl($files)
{
// Find the most recent modified time of all files to include in target filename hash so that
// modifications invalidate browser's caching
$filenamesHash = md5(implode(',', $files));
$newestCacheKey = 'DESIGN_NEWEST_CSS_'.$filenamesHash;
if( ! Mage::app()->useCache(self::CACHE_KEY) || ! ($newest = Mage::app()->loadCache($newestCacheKey))) {
$newest = 0;
foreach($files as $file) {
$mtime = filemtime($file);
if($mtime > $newest) $newest = $mtime;
}
Mage::app()->saveCache($newest, $newestCacheKey, array(self::CACHE_TAG));
}
$targetFilename = md5(implode('-',array(
Mage::getBaseUrl('skin'), // In case skin url is updated
$filenamesHash, // In case file is added or removed
$newest, // In case file is updated
isset($_SERVER['HTTPS']), // Vary for http/https in skin image urls
)));
$targetDir = $this->_initMergerDir('css');
if (!$targetDir) {
return '';
}
$targetPath = $targetDir . DS . $targetFilename;
// Check for minified version (minified by external app)
if(file_exists($targetPath.'-min.css')) {
return $this->_getBaseMergedUrl() . 'css/' . $targetFilename.'-min.css';
}
// Check for non-minified version
else if(file_exists($targetPath.'.css')) {
return $this->_getBaseMergedUrl() . 'css/' . $targetFilename.'.css';
}
// Files need to be merged
if (Mage::helper('core')->mergeFiles($files, $targetPath.'.css', false, array($this, 'beforeMergeCss'), 'css')) {
return $this->_getBaseMergedUrl() . 'css/' . $targetFilename.'.css';
}
return '';
}
/**
* Merge specified javascript files and return URL to the merged file on success
*
* @param $files
* @return string
*/
public function getMergedJsUrl($files)
{
// Find the most recent modified time of all files to include in target filename hash so that
// modifications invalidate browser's caching
$filenamesHash = md5(implode(',', $files));
$newestCacheKey = 'DESIGN_NEWEST_JS_'.$filenamesHash;
if( ! Mage::app()->useCache(self::CACHE_KEY) || ! ($newest = Mage::app()->loadCache($newestCacheKey))) {
$newest = 0;
foreach($files as $file) {
$mtime = filemtime($file);
if($mtime > $newest) $newest = $mtime;
}
Mage::app()->saveCache($newest, $newestCacheKey, array(self::CACHE_TAG));
}
$targetFilename = md5(implode('-',array(
Mage::getBaseUrl('skin'), // In case skin url is updated
$filenamesHash, // In case file is added or removed
$newest, // In case file is updated
)));
$targetDir = $this->_initMergerDir('js');
if (!$targetDir) {
return '';
}
$targetPath = $targetDir . DS . $targetFilename;
// Check for minified version (minified by external app)
if(file_exists($targetPath.'-min.js')) {
return $this->_getBaseMergedUrl() . 'js/' . $targetFilename.'-min.js';
}
// Check for non-minified version
else if(file_exists($targetPath.'.js')) {
return $this->_getBaseMergedUrl() . 'js/' . $targetFilename.'.js';
}
// Files need to be merged
if (Mage::helper('core')->mergeFiles($files, $targetPath.'.js', false, null, 'js')) {
return $this->_getBaseMergedUrl() . 'js/' . $targetFilename.'.js';
}
return '';
}
/**
* Equivalent to Mage::getBaseUrl('merged') without overriding Mage_Core_Model_Store
*
* @return string
*/
protected function _getBaseMergedUrl()
{
if( ! $this->_baseMergedUrl) {
$this->_baseMergedUrl = Mage::app()->getStore()->getConfig(
'web/'.(Mage::app()->getStore()->isCurrentlySecure() ? 'secure' : 'unsecure').'/base_merged_url'
);
}
return $this->_baseMergedUrl;
}
/**
* Overridden to do nothing since there is never any reason to clean these with the new hashing scheme
*
* @return bool
*/
public function cleanMergedJsCss()
{
return TRUE;
}
/**
* Make sure merger dir exists and writeable
* Also can clean it up
* MODIFIED to not remove the directory itself since it is watched by inotify
*
* @param string $dirRelativeName
* @param bool $cleanup
* @return bool|string
*/
protected function _initMergerDir($dirRelativeName, $cleanup = false)
{
try {
$dir = Mage::getBaseDir('media') . DS . $dirRelativeName;
if ($cleanup && is_dir($dir)) {
array_map('unlink', glob("$dir/*.*"));
}
if (!is_dir($dir)) {
mkdir($dir);
}
return is_writeable($dir) ? $dir : false;
} catch (Exception $e) {
Mage::logException($e);
}
return false;
}
/**
* Prepare url for css replacement
* MODIFIED to use real skin url (for CDN compatibility)
*
* @param string $uri
* @return string
*/
protected function _prepareUrl($uri)
{
// check absolute or relative url
if (!preg_match('/^[http|https]/i', $uri) && !preg_match('/^\//i', $uri)) {
$fileDir = '';
$pathParts = explode(DS, $uri);
$fileDirParts = explode(DS, $this->_callbackFileDir);
foreach ($pathParts as $key=>$part) {
if ($part == '.' || $part == '..') {
unset($pathParts[$key]);
}
if ($part == '..' && count($fileDirParts)) {
$fileDirParts = array_slice($fileDirParts, 0, count($fileDirParts) - 1);
}
}
if (count($fileDirParts)) {
$fileDir = implode('/', $fileDirParts).'/';
}
$uri = $fileDir.implode('/', $pathParts);
if (substr($uri,0,5) == 'skin/') {
$uri = Mage::getBaseUrl('skin').substr($uri,5);
} else {
$uri = Mage::getBaseUrl('web').$uri;
}
}
return $uri;
}
/**
* Takes one or two parameters (set all or set one type)
*
* Overridden to allow a theme to be "locked", so that later code cannot override the theme (e.g. for mobile version)
*
* @return \Template_Core_Model_Design_Package
*/
public function setTheme()
{
if($this->_locked) {
return $this;
}
switch (func_num_args()) {
case 1:
foreach (array('layout', 'template', 'skin', 'locale') as $type) {
$this->_theme[$type] = func_get_arg(0);
}
break;
case 2:
$this->_theme[func_get_arg(0)] = func_get_arg(1);
break;
default:
throw Mage::exception(Mage::helper('core')->__('Wrong number of arguments for %s', __METHOD__));
}
return $this;
}
/**
* Overridden to allow a package to be "locked", so that later code cannot override the theme (e.g. for mobile version)
*
* @param string $name
* @return Mage_Core_Model_Design_Package
*/
public function setPackageName($name = '')
{
if($this->_locked) {
return $this;
}
return parent::setPackageName($name);
}
/**
* Locks the theme and package to the existing theme and package
*/
public function lockPackageTheme()
{
$this->_locked = TRUE;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment