├── .gitignore
├── .travis.yml
├── README.md
├── bin
├── install-wp-tests.sh
└── svn-sync.sh
├── composer.json
├── composer.lock
├── docs
├── 2.0.0.md
└── 2.1.0.md
├── phpunit.xml
├── plugin.php
├── readme.txt
├── src
├── AssetsMinify.php
└── AssetsMinify
│ ├── Admin.php
│ ├── Assets
│ ├── Css.php
│ ├── Css
│ │ ├── Css.php
│ │ ├── Less.php
│ │ ├── Sass.php
│ │ └── Scss.php
│ ├── Factory.php
│ ├── Js.php
│ └── Js
│ │ ├── Coffee.php
│ │ └── Js.php
│ ├── Cache.php
│ ├── Cache
│ └── GarbageCollector.php
│ ├── Init.php
│ ├── Log.php
│ └── Pattern
│ └── Singleton.php
├── templates
└── settings.phtml
└── tests
├── AssetsMinify
├── AdminTest.php
├── Assets
│ ├── CssTest.php
│ └── JsTest.php
├── Cache
│ └── GarbageCollectorTest.php
├── CacheTest.php
├── InitTest.php
└── LogTest.php
├── AssetsMinifyTest.php
├── bootstrap.php
└── options.php
/.gitignore:
--------------------------------------------------------------------------------
1 | unittests-config.php
2 | vendor/
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - 5.3
5 | - 5.4
6 |
7 | env:
8 | - WP_VERSION=latest WP_MULTISITE=0
9 | - WP_VERSION=latest WP_MULTISITE=1
10 | - WP_VERSION=3.8 WP_MULTISITE=0
11 | - WP_VERSION=3.8 WP_MULTISITE=1
12 |
13 | before_script:
14 | - composer install
15 | - bash bin/install-wp-tests.sh wordpress_test root '' localhost $WP_VERSION
16 |
17 | script: phpunit
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | AssetsMinify
2 | ============
3 |
4 | [](https://travis-ci.org/acarbone/AssetsMinify)
5 | [](https://bitdeli.com/free "Bitdeli Badge")
6 |
7 | AssetsMinify is a [WordPress plugin](http://wordpress.org/extend/plugins/assetsminify/) based on [Assetic library](https://github.com/kriswallsmith/assetic) to let using Compass, Sass, Less, CoffeeScript (and more ...) for developing themes and for minifying JS and CSS resources.
8 |
9 |
10 | Why use it
11 | -------------
12 |
13 | How many times have you wished to minify in a clean way all the stylesheets and scripts of a WordPress website?
14 | WordPress offers the way to include JS specifying where to import the script ( within `
` for better performances, but not every WordPress plugin's developer is prone to do so. 16 | 17 | AssetsMinify takes every CSS and JS asset included using `wp_enqueue_style()` and `wp_enqueue_script()`, merges and minifies them. 18 | 19 | You can also use AssetsMinify to create your WP theme using Compass / Sass / Less without configuring any `config.rb` or *that kind of stuff*. 20 | 21 | 22 | How it works 23 | ------------- 24 | 25 | You can simply include your stylesheets using the WordPress way: 26 | 27 | ``` php 28 | 35 | This will work! AssetsMinify will compile 'em all and will combine them in a single css file. 36 | 37 | ``` php 38 | ` ( in the previous sample only script1 ) and which would go before `` ( script2 and script3 ). 45 | External scripts are not managed by AssetsMinify (so script4 in the sample will be included with a separate `"; 139 | } 140 | 141 | /** 142 | * Combines the script data from all minified scripts 143 | * 144 | * @param string $where The page's place to dump the scripts in (header or footer) 145 | * @return string The script to include within the page 146 | */ 147 | protected function buildScriptData( $where ) { 148 | $data = ''; 149 | 150 | if ( empty($this->localized[$where]) ) 151 | return ''; 152 | 153 | foreach ($this->localized[$where] as $script) { 154 | $data .= $script; 155 | } 156 | 157 | $asset = new StringAsset( $data, array(new JSqueezeFilter) ); 158 | return $asset->dump(); 159 | } 160 | 161 | /** 162 | * Prints \n"; 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/AssetsMinify/Assets/Js/Coffee.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class Coffee extends \AssetsMinify\Assets\Factory { 12 | /** 13 | * Constructor 14 | * 15 | * @param array $content The files to save to cache 16 | * @param string $cachefile The cache file name 17 | * @param object $manager The Factory object 18 | */ 19 | public function __construct($content, $cachefile, $manager) { 20 | parent::__construct( $this ); 21 | $manager->cache->fs->set( $cachefile, $this->createAsset( $content, $this->getFilters() )->dump() ); 22 | } 23 | 24 | public function setFilters() { 25 | $this->setFilter('CoffeeScriptFilter', new CoffeeScriptFilter( get_option('am_coffeescript_path', '/usr/bin/coffee') ) ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/AssetsMinify/Assets/Js/Js.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | class Js { 10 | /** 11 | * Constructor 12 | * 13 | * @param array $content The files to save to cache 14 | * @param string $cachefile The cache file name 15 | * @param object $manager The Factory object 16 | */ 17 | public function __construct($content, $cachefile, $manager) { 18 | $manager->cache->fs->set( $cachefile, $manager->createAsset( $content, $manager->getFilters() )->dump() ); 19 | } 20 | } -------------------------------------------------------------------------------- /src/AssetsMinify/Cache.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class Cache { 13 | 14 | protected $path, 15 | $url, 16 | $wp_upload_dir, 17 | $updated = false; 18 | 19 | public static $directory = 'am_assets'; 20 | public $fs, 21 | $gc; 22 | 23 | /** 24 | * Constructor 25 | */ 26 | public function __construct() { 27 | //WordPress directories detection 28 | $this->wp_upload_dir = wp_upload_dir(); 29 | $this->url = str_replace( 'http://', '//', $this->wp_upload_dir['baseurl'] ) . '/' . self::$directory . '/'; 30 | $this->path = $this->wp_upload_dir['basedir']; 31 | 32 | //Creates the uploads dir 33 | if ( !is_dir($this->path) ) { 34 | mkdir($this->path, 0777); 35 | } 36 | 37 | $this->path .= '/' . self::$directory . '/'; 38 | 39 | //Creates the AM cache dir 40 | if ( !is_dir($this->path) ) { 41 | mkdir($this->path, 0777); 42 | } else { 43 | //Calls the Garbage Collector that outdated cached files. 44 | $this->gc = new Cache\GarbageCollector( $this ); 45 | } 46 | 47 | //Manager for Filesystem management 48 | $this->fs = new FilesystemCache( $this->path ); 49 | } 50 | 51 | /** 52 | * Gets the AssetsMinify cache path 53 | * 54 | * @return string 55 | */ 56 | public function getPath() { 57 | return $this->path; 58 | } 59 | 60 | /** 61 | * Gets the AssetsMinify cache url 62 | * 63 | * @return string 64 | */ 65 | public function getUrl() { 66 | return $this->url; 67 | } 68 | 69 | /** 70 | * Sets the updated status for the cache 71 | * 72 | * @return true 73 | */ 74 | public function update() { 75 | return $this->updated = true; 76 | } 77 | 78 | /** 79 | * Gets the cache status 80 | * 81 | * @return boolean True if cache has been updated 82 | */ 83 | public function isUpdated() { 84 | return $this->updated; 85 | } 86 | 87 | /** 88 | * Flush the whole cache 89 | * 90 | * @return true 91 | */ 92 | public function flush() { 93 | $uploadsDir = wp_upload_dir(); 94 | $filesList = glob($this->path . "*.*"); 95 | if ( $filesList !== false ) { 96 | array_map('unlink', $filesList); 97 | } 98 | return true; 99 | } 100 | } -------------------------------------------------------------------------------- /src/AssetsMinify/Cache/GarbageCollector.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class GarbageCollector { 11 | 12 | protected $offset = 86400; 13 | 14 | public static $code = 'am_last_gc'; 15 | public static $period = 10; 16 | 17 | /** 18 | * Constructor. Checks if the last check provided is < $period days ago 19 | * 20 | * @param object $cache The cache manager used to get the cache path 21 | */ 22 | public function __construct($cache) { 23 | $this->cache = $cache; 24 | $this->offset *= self::$period; 25 | 26 | //Every {$period} days the cache is refreshed 27 | if ( get_option( self::$code, 0 ) <= time() - $this->offset ) { 28 | $this->refresh(); 29 | } 30 | 31 | update_option( self::$code, time() ); 32 | } 33 | 34 | /** 35 | * Refreshes the outdated cached files 36 | */ 37 | public function refresh() { 38 | $files = glob( $this->cache->getPath() . "*.*" ); 39 | if ( $files === false ) 40 | return false; 41 | 42 | foreach ( $files as $filepath ) { 43 | //If the file is older than $period days then is removed 44 | if ( filemtime($filepath) <= time() - $this->offset ) { 45 | unlink($filepath); 46 | } 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /src/AssetsMinify/Init.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class Init { 14 | 15 | public $js, 16 | $css; 17 | 18 | protected $exclusions; 19 | 20 | /** 21 | * Constructor 22 | */ 23 | public function __construct() { 24 | // Cache manager 25 | $this->cache = new Cache; 26 | 27 | if ( get_option('am_dev_mode', 0) ) 28 | $this->cache->flush(); 29 | 30 | // Assets managers for Js and Css 31 | $this->js = new Js($this); 32 | $this->css = new Css($this); 33 | 34 | // Log manager 35 | $this->log = \AssetsMinify\Log::getInstance($this->cache); 36 | 37 | $this->exclusions = preg_split('/[ ]*,[ ]*/', trim(get_option('am_files_to_exclude'))); 38 | 39 | //Detects all js and css added to WordPress and removes their inclusion 40 | if( get_option('am_compress_styles', 1) ) 41 | add_action( 'wp_print_styles', array( $this->css, 'extract' ) ); 42 | if( get_option('am_compress_scripts', 1) ) 43 | add_action( 'wp_print_scripts', array( $this->js, 'extract' ) ); 44 | 45 | //Inclusion of scripts in
and before 46 | add_action( 'wp_head', array( $this, 'header' ) ); 47 | add_action( 'wp_footer', array( $this, 'footer' ) ); 48 | } 49 | 50 | /** 51 | * Checks if a file is within the list of resources to exclude 52 | * 53 | * @param string $path The file path 54 | * @return boolean Whether the file is to exclude or not 55 | */ 56 | public function isFileExcluded( $path ) { 57 | $filename = explode('/', $path); 58 | if ( in_array($filename[ count($filename) - 1 ], $this->exclusions) ) 59 | return true; 60 | 61 | return false; 62 | } 63 | 64 | /** 65 | * Returns header's inclusion for CSS and JS 66 | */ 67 | public function header() { 68 | $this->css->generate(); 69 | $this->js->generate('header'); 70 | } 71 | 72 | /** 73 | * Returns footer's inclusion for JS 74 | */ 75 | public function footer() { 76 | $this->js->generate('footer'); 77 | if ( $this->cache->isUpdated() ) 78 | Log::getInstance()->dumpStorage(); 79 | } 80 | } -------------------------------------------------------------------------------- /src/AssetsMinify/Log.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class Log extends Pattern\Singleton { 16 | 17 | protected $active, 18 | $cache, 19 | $logger, 20 | $storage; 21 | 22 | public static $filename = 'compile.log'; 23 | 24 | /** 25 | * Constructor 26 | * 27 | * @param object $cache The cache instance 28 | * @return false if logging is disabled 29 | */ 30 | protected function __construct($params) { 31 | $this->active = get_option('am_log', 0) == 1; 32 | 33 | if ( empty($params) ) 34 | return false; 35 | 36 | $cache = $params[0]; 37 | 38 | if ( !$cache ) 39 | return false; 40 | 41 | if ( !$this->isActive() ) 42 | return false; 43 | 44 | $this->cache = $cache; 45 | 46 | $this->checkSize(); 47 | 48 | // create a log channel 49 | $this->logger = new Logger('Compile'); 50 | $this->logger->pushHandler( new StreamHandler( $this->getFilePath(), Logger::DEBUG ) ); 51 | } 52 | 53 | /** 54 | * The path of the log file 55 | * 56 | * @param string 57 | */ 58 | public function getFilePath() { 59 | return $this->cache->getPath() . self::$filename; 60 | } 61 | 62 | /** 63 | * The path of the log file 64 | * 65 | * @param string 66 | */ 67 | public function checkSize() { 68 | $filepath = $this->getFilePath(); 69 | 70 | if ( file_exists($filepath) && filesize($filepath) > 10000000 ) 71 | $this->flush(); 72 | } 73 | 74 | /** 75 | * Flush the log 76 | * 77 | * @return boolean True if log has been flushed 78 | */ 79 | public function flush() { 80 | unlink($this->getFilePath()); 81 | return true; 82 | } 83 | 84 | /** 85 | * Log an info level message. 86 | * 87 | * @param string $message The message to log 88 | * @return boolean True if log has been written, only if logging is enabled 89 | */ 90 | public function info($message) { 91 | if ( !$this->isActive() ) 92 | return false; 93 | 94 | $this->logger->info($message); 95 | return true; 96 | } 97 | 98 | /** 99 | * Get all saved logs 100 | * 101 | * @return array 102 | */ 103 | public function getAll() { 104 | return new LogReader( $this->getFilePath() ); 105 | } 106 | 107 | /** 108 | * True if logging is enabled. 109 | * 110 | * @return true if logging is enabled 111 | */ 112 | public function isActive() { 113 | return $this->active; 114 | } 115 | 116 | /** 117 | * Store a key, value pair 118 | * 119 | * @param string $key 120 | * @param mixed $value 121 | * @return string The set value 122 | */ 123 | public function set($key, $value) { 124 | $this->storage[$key] = $value; 125 | return $value; 126 | } 127 | 128 | /** 129 | * Retrieve a value saved within storage 130 | * 131 | * @param string $key 132 | * @return mixed False if key isn't set else the value 133 | */ 134 | public function get($key) { 135 | return isset($this->storage[$key]) ? $this->storage[$key] : false; 136 | } 137 | 138 | /** 139 | * Log the storage's content profiling the timing performances saved within it 140 | * 141 | * @return true 142 | */ 143 | public function dumpStorage() { 144 | foreach ( $this->storage as $key => $value ) { 145 | $line = "$key: "; 146 | if ( is_string($value) ) { 147 | $line .= $value; 148 | } else if ( is_array($value) && count($value) === 2 ) { 149 | $line .= ($value[1] - $value[0]) . "s"; 150 | } else { 151 | $line .= json_encode($value); 152 | } 153 | $this->info($line); 154 | } 155 | 156 | $this->storage = array(); 157 | return true; 158 | } 159 | } -------------------------------------------------------------------------------- /src/AssetsMinify/Pattern/Singleton.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | abstract class Singleton { 12 | protected function __construct() {} 13 | 14 | /** 15 | * Method to get the single object instantiated for the class 16 | * 17 | * @final 18 | * @static 19 | */ 20 | final public static function getInstance() { 21 | static $instances = array(); 22 | 23 | $calledClass = get_called_class(); 24 | 25 | if ( !isset($instances[$calledClass]) ) { 26 | $instances[$calledClass] = new $calledClass(func_get_args()); 27 | } 28 | 29 | return $instances[$calledClass]; 30 | } 31 | 32 | final private function __clone() {} 33 | } -------------------------------------------------------------------------------- /templates/settings.phtml: -------------------------------------------------------------------------------- 1 |
5 | 106 |
107 | -------------------------------------------------------------------------------- /tests/AssetsMinify/AdminTest.php: -------------------------------------------------------------------------------- 1 | admin = new AssetsMinify\Admin; 11 | } 12 | 13 | public function testSettings() { 14 | ob_start(); 15 | $this->admin->settings(); 16 | $settingsTpl = ob_get_clean(); 17 | $this->assertContains( "", $settingsTpl ); 18 | } 19 | 20 | public function testOptions() { 21 | $this->assertInternalType( "array", $this->admin->options() ); 22 | $this->assertGreaterThan( 0, count($this->admin->options()) ); 23 | } 24 | } -------------------------------------------------------------------------------- /tests/AssetsMinify/Assets/CssTest.php: -------------------------------------------------------------------------------- 1 | manager = new AssetsMinify\Init; 9 | $this->css = new AssetsMinify\Assets\Css($this->manager); 10 | } 11 | 12 | public function testFilters() { 13 | $filters = $this->css->getFilters(); 14 | $this->assertTrue( is_array($filters) ); 15 | $this->assertEquals( 'CssMin', $filters[0] ); 16 | $this->assertEquals( 'CssRewrite', $filters[1] ); 17 | $this->assertFalse( isset($filters[2]) ); 18 | } 19 | 20 | public function testExtract() { 21 | global $wp_styles; 22 | wp_enqueue_style( 'style-extract', get_stylesheet_uri() ); 23 | 24 | $external = 0; 25 | foreach( $wp_styles->queue as $handle ) { 26 | $style = str_replace( get_site_url(), "", $wp_styles->registered[$handle]->src ); 27 | if ( strpos($style, "http") === 0 || strpos($style, "//") === 0 ) { 28 | $external++; 29 | continue; 30 | } 31 | } 32 | $this->css->extract(); 33 | $this->assertEquals( count($wp_styles->queue), $external ); 34 | } 35 | 36 | public function testGenerate() { 37 | wp_enqueue_style( 'style-generate', get_stylesheet_uri() ); 38 | $this->css->extract(); 39 | 40 | ob_start(); 41 | $this->css->generate(); 42 | $dump = ob_get_clean(); 43 | $this->assertStringStartsWith( "assertContains( "media='print'", $dump ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tests/AssetsMinify/Assets/JsTest.php: -------------------------------------------------------------------------------- 1 | manager = new AssetsMinify\Init; 9 | $this->js = new AssetsMinify\Assets\Js($this->manager); 10 | } 11 | 12 | public function testFilters() { 13 | $filters = $this->js->getFilters(); 14 | $this->assertTrue( is_array($filters) ); 15 | $this->assertEquals( 'JSqueeze', $filters[0] ); 16 | $this->assertFalse( isset($filters[1]) ); 17 | } 18 | 19 | public function testExtract() { 20 | global $wp_scripts; 21 | wp_enqueue_script( 'script-extract', get_template_directory_uri() . '/js/functions.js', array(), '1.0', true ); 22 | 23 | $external = 0; 24 | foreach( $wp_scripts->queue as $handle ) { 25 | $script = str_replace( get_site_url(), "", $wp_scripts->registered[$handle]->src ); 26 | if ( strpos($script, "http") === 0 || strpos($script, "//") === 0 ) { 27 | $external++; 28 | continue; 29 | } 30 | } 31 | $this->js->extract(); 32 | $this->assertEquals( count($wp_scripts->queue), $external ); 33 | } 34 | 35 | public function testGenerate() { 36 | wp_enqueue_script( 'script-generate', get_template_directory_uri() . '/js/functions.js', array(), '1.0', true ); 37 | $this->js->extract(); 38 | 39 | ob_start(); 40 | $this->js->generate( 'footer' ); 41 | $dump = ob_get_clean(); 42 | $this->assertStringStartsWith( "