Permalink
Find file
Fetching contributors…
Cannot retrieve contributors at this time
457 lines (386 sloc) 12.8 KB
<?php
/**
* Initialize peroxide
*/
function peroxide_init($theme) {
$file = dirname($theme->filename) . '/template.php';
if (file_exists($file)) {
include_once "./$file";
}
// Set Haml parser options
if (!empty($theme->info['peroxide']['options']['haml'])) {
_peroxide_set_haml_options($theme, $theme->info['peroxide']['options']['haml']);
}
else {
_peroxide_set_haml_options($theme);
}
// Set Sass parser options
if (!empty($theme->info['peroxide']['options']['sass'])) {
_peroxide_set_sass_options($theme, $theme->info['peroxide']['options']['sass']);
}
else {
_peroxide_set_sass_options($theme);
}
// Initialize parsers and render Sass
_peroxide_init();
// If the theme implements hook_css_alter(), assume it calls peroxide_css_alter()
if(!function_exists("{$theme->name}_css_alter")) {
_peroxide_scan_sass($theme);
}
_peroxide_scan_less($theme);
}
/**
* The extension for our templates
*/
function peroxide_extension() {
return ".haml";
}
/**
* We're handling HAML template files
*/
function peroxide_theme($existing, $type, $theme, $path) {
$templates = drupal_find_theme_functions($existing, array('haml', $theme));
$templates += drupal_find_theme_templates($existing, '.haml', $path);
return $templates;
}
/**
* Render a HAML template
*/
function peroxide_render_template($template, $vars) {
// Attempt to make a logical cache structure, if that doesn't work then throw it out the window
// Assuming directory is always set this should handle multiple themes well
if (isset($vars['directory'])) {
$cached_haml_path = file_default_scheme() . '://peroxide/haml_c/' . basename($vars['directory']);
}
else {
$cached_haml_path = file_default_scheme() . '://peroxide/haml_c/';
}
// Make sure that the directory we're placing css files in exists, if it doesn't exist attempt to create it
_peroxide_check_directory($cached_haml_path);
// Retrieve options for the Haml parser
$options = _peroxide_get_haml_options();
// Extract Variables
extract($vars, EXTR_SKIP);
// Render the template
ob_start();
$parser = new HamlParser($options);
include $parser->parse($template, $cached_haml_path, 0755, '.haml', '.tpl.php');
$contents = ob_get_contents();
ob_end_clean();
// Return contents
return $contents;
}
/**
* Implements hook_css_alter. Call it from your theme's hook_css_alter.
*
* Processes and .sass/.scss files in the stylesheets[] array.
*/
function peroxide_css_alter(&$css) {
$themes = list_themes();
$theme_info = $themes[$GLOBALS['theme']];
foreach($css as $fn => $info) {
$pathinfo = pathinfo($fn);
if($pathinfo['extension'] == 'scss' || $pathinfo['extension'] == 'sass') {
$new_fn = _peroxide_process_sass($theme_info, $fn);
unset($css[$fn]);
if($new_fn) {
$info['data'] = $new_fn;
$info['preprocess'] = FALSE;
$css[$new_fn] = $info;
}
}
}
}
/**
* Process a file from the stylesheets[] array. Called from peroxide_css_alter().
* The new file will be in the same directory, with the extension .sass-cache.css
*
* @param $theme Theme info object
* @param $sass Filename of the sass/scss file (from stylesheets[])
* @returns The filename of the processed file
*/
function _peroxide_process_sass($theme, $sass) {
$info = pathinfo($sass);
// We put the new file in the same place because it's easier than reimplementing Drupal's url() rewriting
$css_file = "{$info['dirname']}/{$info['filename']}.sass-cache.css";
if(filemtime($css_file) < filemtime($file)) {
return $css_file;
}
// Try and build the file with the Ruby version
@exec(
'sass '.($info['extension'] == 'scss' ? '--scss ' : '').
escapeshellarg($sass).' '.escapeshellarg($css_file),
$op, $ret
);
if($ret == 0) {
return $css_file;
}
try {
// Setup the Sass Parser
$options = _peroxide_get_sass_options($theme);
$parser = new SassParser($options);
$css = $parser->parse($sass)->render();
}
catch (SassException $e) {
drupal_set_message($e->getMessage(), 'error');
drupal_set_message($e->getTraceAsString(), 'error');
}
file_put_contents($css_file, $css);
return $css_file;
}
/**
* Initialize the Haml and Sass Parsers
*
*/
function _peroxide_init() {
$path = drupal_get_path('theme_engine', 'peroxide');
include_once $path . 'phamlp/haml/HamlParser.php';
// Test for Ruby version first
@exec("sass -v", $op, $ret);
if($ret != 0) {
include_once $path . 'phamlp/sass/SassParser.php';
}
include_once $path . 'lessphp/lessc.inc.php';
}
/**
* Scan for sass files, produce css files and then add them to the page
*/
function _peroxide_scan_sass($theme) {
// Setup initial file paths
$path = drupal_get_path('theme', $theme->name);
$cached_css_path = file_default_scheme() . '://peroxide/css/' . $theme->name;
// Make sure that the directory we're placing css files in exists, if it doesn't exist attempt to create it
_peroxide_check_directory($cached_css_path);
// Setup the Sass Parser
$options = _peroxide_get_sass_options($theme);
$parser = new SassParser($options);
// Read information about sass files from the theme's info file
if (!empty($theme->info['sass'])) {
foreach ($theme->info['sass'] as $media => $sassy) {
foreach ($sassy as $sass) {
$sass_path = $path . '/' . $sass;
$info = pathinfo($sass_path);
$css_file = $cached_css_path . '/' . $info['filename'] . '.css';
try {
$css = $parser->parse($sass_path)->render();
}
catch (SassException $e) {
drupal_set_message($e->getMessage(), 'error');
drupal_set_message($e->getTraceAsString(), 'error');
}
// rewrite asset paths if the theme's settings specify to do so
if ($theme->info['peroxide']['options']['sass']['rewrite asset paths'] == 'true'
|| (is_array($theme->info['peroxide']['options']['sass']['rewrite asset paths'])
&& in_array($sass, $theme->info['peroxide']['options']['sass']['rewrite asset paths']))) {
$matches = array();
if (preg_match_all('/url\([\'"](?P<url>.+[\'"])\)/', $css, $matches)) {
foreach ($matches['url'] as $url) {
$url = str_replace(array("'", '"'), '', $url); // remove single and double quotes which sometimes remain
// for now, just pop off the last element since there's never a time you'd reference a directory and not a file inside a css url()
$url = explode('/', $url);
$filename = array_pop($url);
$url = implode('/', $url);
// calculate the new relative path
$sass_dir = dirname($sass_path); // if sass[all][] = src/mysass.sass is specified, $sass_dir is "src"
$new_url = peroxide_calculate_path_difference($sass_dir . '/' . $url, $cached_css_path) . "/$filename";
// do the actual replacement. we either have to do str_replace twice or preg_replace once.
$css = str_replace("url('$url/$filename')", "url('$new_url')", $css);
$css = str_replace("url(\"$url/$filename\")", "url(\"$new_url\")", $css);
}
}
}
// cache CSS in a file
file_put_contents($css_file, $css);
if ($media != 'compile-only') {
drupal_add_css($css_file, 'theme', $media);
}
}
}
}
}
function _peroxide_scan_less($theme) {
// Setup initial file paths
$path = drupal_get_path('theme', $theme->name);
$cached_css_path = file_default_scheme() . '://peroxide/css/' . $theme->name;
// Read information about sass files from the theme's info file
if (!empty($theme->info['less'])) {
foreach ($theme->info['less'] as $media => $lessc) {
foreach ($lessc as $less) {
$less_path = $path . '/' . $less;
$info = pathinfo($less_path);
$css_file = $cached_css_path . '/' . $info['filename'] . ".css";
// Compile the less file
try {
lessc::ccompile($less_path, $css_file);
}
catch (Exception $e) {
drupal_set_message($e->getMessage(), 'error');
}
// Add to the page
drupal_add_css($css_file, 'theme', $media);
}
}
}
}
/**
* Check to see if a directory exists, if it doesn't
* attempt to recursively create the necessary directories.
*/
function _peroxide_check_directory($path) {
$dir_exists = file_prepare_directory($path);
if (!$dir_exists) {
$result = mkdir($path, 0755, TRUE);
if (!$result) {
drupal_set_message('You must have your Drupal files directory correctly configured to use peroxide.', 'error');
return;
}
}
else {
$result = TRUE;
}
return $result;
}
/**
* Set options for the Haml parser.
*/
function _peroxide_set_haml_options($theme = array(), $options = array()) {
$set_options = &drupal_static(__FUNCTION__);
// If no theme was passed in then return the options that have been set
if (!empty($set_options)) {
return $set_options;
}
// Merge options from theme's info file with the defaults
$set_options = array_merge(_peroxide_default_haml_options(), $options);
// Allow modules & running theme to alter Haml parser options
peroxide_alter('haml_options', $set_options, $theme);
return $set_options;
}
/**
* Get options for the Haml parser
*/
function _peroxide_get_haml_options() {
return _peroxide_set_haml_options();
}
/**
* Default options for the Haml parser.
*/
function _peroxide_default_haml_options() {
$options = array(
'style' => 'nested',
);
return $options;
}
/**
* Set options for the sass parser
*/
function _peroxide_set_sass_options($theme = array(), $options = array()) {
$set_options = drupal_static(__FUNCTION__);
if(!empty($set_options)) {
return $set_options;
}
// Merge options from theme's info file with the defaults
$set_options = array_merge(_peroxide_default_sass_options($theme), $options);
// Allow modules & running theme to alter options
peroxide_alter('sass_options', $set_options, $theme);
return $set_options;
}
/**
* Retrieve options for the Sass parser.
*/
function _peroxide_get_sass_options($theme) {
return _peroxide_set_sass_options($theme);
}
/**
* Default options for the Sass parser.
*/
function _peroxide_default_sass_options() {
$options = array(
'cache_location' => file_directory_temp(),
'css_location' => '/css',
'extensions' => array(
'compass' => array(),
),
);
return $options;
}
/**
* A function to allow alteration of underlying parser options by
* the theme using peroxide at runtime. Also allows modules
* to alter options as well.
*
* @param $hook
* The name of the alteration hook (e.g. haml_options)
* @param $theme
* Information for the theme.
* @param $options
* The options for the underlying parser.
*/
function peroxide_alter($hook, &$options, $theme) {
$hook = 'peroxide_' . $hook;
// Allow modules to alter options
drupal_alter($hook, $options, $theme);
// Allow theme to alter options
$fun = $theme->name . '_' . $hook . '_alter';
if (function_exists($fun)) {
$fun($options, $theme);
}
}
/**
* Finds the relative path from $root to $dest.
*
* @param $dest
* The destination.
* @param $root
* The starting point.
* @param $dir_sep
* The string used to separate directories. Defaults to '/'.
*/
function peroxide_calculate_path_difference($dest, $root = '', $dir_sep = '/') {
$root = explode($dir_sep, $root);
$dest = explode($dir_sep, $dest);
$path = '.';
$fix = '';
$diff = 0;
// calculate the relative path
for ($i = -1; ++$i < max(($rC = count($root)), ($dC = count($dest))); ) {
if (isset($root[$i]) and isset($dest[$i])) {
if ($diff) {
$path .= $dir_sep. '..';
$fix .= $dir_sep. $dest[$i];
continue;
}
if ($root[$i] != $dest[$i]) {
$diff = 1;
$path .= $dir_sep. '..';
$fix .= $dir_sep. $dest[$i];
continue;
}
}
else if (!isset($root[$i]) and isset($dest[$i])) {
for($j = $i-1; ++$j < $dC;) {
$fix .= $dir_sep. $dest[$j];
}
break;
}
else if (isset($root[$i]) and !isset($dest[$i])) {
for($j = $i-1; ++$j < $rC;) {
$fix = $dir_sep. '..'. $fix;
}
break;
}
}
$path .= $fix;
// clean the path of all unnecessary relative path components (".", "..")
$path = preg_replace('~/\./~', '/', $path); // resolve "."
// resolve ".."
$parts = array();
foreach (explode('/', preg_replace('~/+~', '/', $path)) as $part) {
if ($part == '..' && count($parts) > 0 && $parts[count($parts) - 1] != '..') {
$x = array_pop($parts);
}
else if ($part != '' && $part != '.') {
$parts[] = $part;
}
}
return implode('/', $parts);
}