Calling functions within your CSS files

I always thought CSS should have a little bit more power, like the use of inline programming functions and dynamic variables. That's exactly why I set out to write my Compression class. On top of compressing the CSS files, saving cached versions and assigning dynamic variables, the class can now call PHP functions that have been written inline within the CSS itself.

This allows for tons of new possibilities for allowing dynamic content, values and structure within boring old stylesheets. As an example, here is the PHP function, the CSS written with the function and the result.

// The PHP function
public function colWidth() {
	$args = func_get_args(); 
	$width = $args[0] * 100;
	return $width .'px';
}
// The CSS
.col1 { width: colWidth(5); }
.col2 { width: colWidth(3); }
.col3 { width: colWidth(1); }
// After being parsed
.col1 { width: 500px; }
.col2 { width: 300px; }
.col3 { width: 100px; }

This new feature has been released in the new Compression version 1.4, which is now available! You can view the updated documentation, or download the files to see the example usages, enjoy! Be sure to spread the word!

Download Compression v1.4

New scripts galore

It has been quite a while since I posted a real entry, I just haven't worked in CakePHP for the past few weeks. Hopefully these two new scripts and the new script versions will make up for it. Be sure to updated your Databasic if you are using it!

New Scripts

Compression
Compression is a light weight class that will load a CSS stylesheet, bind and translate given variables, compress and remove white space and cache the output for future use. Upon each request will determine if the cached file should be loaded if the original has had no modifications.

Formation
Formation is a lightweight class that can build all necessary elements for a form, process the posted data (post, get, request), validate the data based on a schema and finally return a cleaned result. Has logic for error handling and applying a class to invalid fields. Additionally, all validator functions are static and can be used externally.

Updated Scripts

Databasic v2.3
Added more support for specific operators and rewrote how statement conditions are parsed. View the full changelog.

CSS/JS/Asset Compression in CakePHP

This code was developed for CakePHP 1.2 and will no longer work on the most recent versions. You should be using an asset handler plugin in 1.3+.

The other day I spent way too many hours trying to figure out CakePHPs built in CSS and JS compression system. To say the least, its not really built in, you have to configure and do a lot of it yourself. I did much searching and found tutorials that didn't work, or using custom built helpers that were of no use, and in the end I wrote my own script (based off webroot/css.php written by gwoo). My script does the following:

  • Compresses both CSS and JS, using one file
  • Caches the assets, and recaches if a change is made to the original
  • Comes prebuilt with CSS compression
  • Must download and install JSMin
  • Extremely easy to install and configure

To use my asset compression script, create a file called assets.php within webroot, and then copy and paste the code found at the end of this post. Once you have done this, download and place the jsmin.php file within /vendors/ or /app/vendors/ (the JSMin file should be named jsmin.php to work). The next step is to create a folder called assets within your /app/tmp/cache/ directory, and chmod the permissions to 777. The final step is to uncomment the filtering in core.php and set the path to assets.php.

Configure::write('Asset.filter.css', 'assets.php');
Configure::write('Asset.filter.js', 'assets.php');

Now to take it for a test spin! Once you have uncommented the configuration, direct your browser to the actual JS and CSS paths to see if it works (http://www.domain.com/ccss/style.css), and that's it! Hope this has been helpful and easy, like it should have been!

<?php 
// No cake installation
if (!defined('CAKE_CORE_INCLUDE_PATH')) { 
	header('HTTP/1.1 404 Not Found');
	exit('File Not Found'); 
} 
// Get asset type
$ext = trim(strrchr($url, '.'), '.');
$assetType = ($ext === 'css') ? 'css' : 'js';
// Wrong file
if (preg_match('|\.\.|', $url) || !preg_match('|^c'. $assetType .'/(.+)$|i', $url, $regs)) {
	die('Wrong File Name');
}
$cachePath = CACHE .'assets'. DS . str_replace(array('/','\\'), '-', $regs[1]);
$fileName = $assetType .'/'. $regs[1];
if ($assetType == 'css') {
	$filePath = CSS . $regs[1];
	$fileType = 'text/css';
} else {
	$filePath = JS . $regs[1];
	$fileType = 'text/javascript';
}
if (!file_exists($filePath)) {
	die('Asset Not Found');
}
/**
 * Compress the asset
 * @param string $path
 * @param string $name
 * @return string
 */
public function compress($path, $name, $type) {
	$input = file_get_contents($path);
	if ($type == 'css') {
		$stylesheet = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $input);
		$stylesheet = str_replace(array("\r\n", "\r", "\n", "\t", '/\s\s+/', '  ', '   '), '', $stylesheet);
		$stylesheet = str_replace(array(' {', '{ '), '{', $stylesheet);
		$stylesheet = str_replace(array(' }', '} '), '}', $stylesheet);
		$output = $stylesheet;
	} else {
		App::import('Vendor', 'jsmin');
		$output = JSMin::minify($input);
	}
	$ratio = 100 - (round(strlen($output) / strlen($input), 3) * 100);
	$output = "/* File: $name, Ratio: $ratio% */\n". $output;
	return $output;
}	
/**
 * Cache the asset
 * @param string $path
 * @param string $content
 * @return string
 */
public function cacheAsset($path, $content) {
	if (!is_dir(dirname($path))) {
		mkdir(dirname($path));
		chmod($path, 0777);
	}
	if (!class_exists('File')) {
		uses('file');
	}
	$cached = new File($path);
	return $cached->write($content);
}
// Do compression and cacheing
if (file_exists($cachePath)) {
	$templateModified = filemtime($filePath);
	$cachedModified = filemtime($cachePath);
	if ($templateModified > $cachedModified) {
		$output = compress($filePath, $fileName, $assetType);
		cacheAsset($cachePath, $output);
	} else {
		$output = file_get_contents($cachePath);
	}
} else {
	$output = compress($filePath, $fileName, $assetType);
	cacheAsset($cachePath, $output);
	$templateModified = time();
}
header("Date: ". date("D, j M Y G:i:s", $templateModified) ." GMT");
header("Content-Type: ". $fileType);
header("Expires: ". gmdate("D, j M Y H:i:s", time() + DAY) ." GMT");
header("Cache-Control: max-age=86400, must-revalidate"); // HTTP/1.1
header("Pragma: cache_asset");        // HTTP/1.0
echo $output; ?>