| 1 |
<?php
|
| 2 |
// $Id:$
|
| 3 |
|
| 4 |
/**
|
| 5 |
@TODO
|
| 6 |
- check that the files/dcss directory...
|
| 7 |
*/
|
| 8 |
|
| 9 |
|
| 10 |
// **************************************************************************
|
| 11 |
// CONSTANTS **************************************************************
|
| 12 |
// **************************************************************************
|
| 13 |
|
| 14 |
/**
|
| 15 |
* These define the various types of overrides for a given sheet
|
| 16 |
*/
|
| 17 |
|
| 18 |
define('CSS_NORMAL', 0);
|
| 19 |
define('CSS_REMOVED', 1);
|
| 20 |
define('CSS_EDITABLE', 2);
|
| 21 |
define('CSS_ADDED', 3); // currently unused
|
| 22 |
|
| 23 |
/**
|
| 24 |
* @NOTES
|
| 25 |
* variables set:
|
| 26 |
* - cssapi_css_list_{$theme} - the listing of all CSS files "seen" with this theme enabled
|
| 27 |
* - cssapi_overrides_{$theme} - the list of overridden files - key is path, val is type
|
| 28 |
*/
|
| 29 |
|
| 30 |
|
| 31 |
// **************************************************************************
|
| 32 |
// HOOKS ******************************************************************
|
| 33 |
// **************************************************************************
|
| 34 |
|
| 35 |
/**
|
| 36 |
* Implementation of hook_preprocess_page
|
| 37 |
*
|
| 38 |
* Note that we're using hook_theme_registry_alter() to
|
| 39 |
* run this AFTER the theme's preprocess_page function
|
| 40 |
*/
|
| 41 |
function cssapi_preprocess_page(&$vars) {
|
| 42 |
$theme = $GLOBALS['theme'];
|
| 43 |
$overrides = variable_get('cssapi_overrides_'. $theme, array());
|
| 44 |
$known_css = variable_get('cssapi_css_list_'. $theme, array());
|
| 45 |
$new_css = $known_css;
|
| 46 |
$old_files = array();
|
| 47 |
$altered = FALSE;
|
| 48 |
// go through all of the css files added to the page and see if there are overrides
|
| 49 |
foreach ($vars['css'] as $media => $types) {
|
| 50 |
foreach ($vars['css'][$media] as $type => $files) {
|
| 51 |
foreach (array_keys($files) as $file) {
|
| 52 |
if (!in_array($file, $known_css)) {
|
| 53 |
// if we've found a new CSS file, add it to the list
|
| 54 |
$new_css[] = $file;
|
| 55 |
}
|
| 56 |
// see if this file is overridden
|
| 57 |
if (isset($overrides[$file])) {
|
| 58 |
$altered = TRUE;
|
| 59 |
switch ($overrides[$file]) {
|
| 60 |
|
| 61 |
case CSS_REMOVED :
|
| 62 |
// remove it
|
| 63 |
unset($vars['css'][$media][$type][$file]);
|
| 64 |
break;
|
| 65 |
|
| 66 |
case CSS_EDITABLE :
|
| 67 |
// swap out for the edited version
|
| 68 |
// grrr.... I really wish there were a way to edit/remove the static $css variable inside of drupal_add_css()
|
| 69 |
$replacement = cssapi_get_editable_path($file, $theme, TRUE);
|
| 70 |
if ($replacement) {
|
| 71 |
// this next line causes CSS to NOT aggregate for users with 'display CSS' permissions
|
| 72 |
$vars['css'][$media][$type][$replacement] = user_access('display CSS') ? 0 : $vars['css'][$media][$type][$file];
|
| 73 |
//$old_files['css'][$media][$type][$replacement] = $file;
|
| 74 |
|
| 75 |
// store a relationship between the old file name and the new (md5 hash version)
|
| 76 |
$pattern = '/([^\/]*?\.css)/i';
|
| 77 |
preg_match($pattern, $replacement, $rep_matches);
|
| 78 |
preg_match($pattern, $file, $orig_matches);
|
| 79 |
$css_map[$rep_matches[1]] = $orig_matches[1];
|
| 80 |
|
| 81 |
unset($vars['css'][$media][$type][$file]);
|
| 82 |
}
|
| 83 |
break;
|
| 84 |
}
|
| 85 |
}
|
| 86 |
}
|
| 87 |
}
|
| 88 |
}
|
| 89 |
// if we've found new css files, re-create the css_list variable
|
| 90 |
if (count($known_css) != count($new_css)) {
|
| 91 |
variable_set('cssapi_css_list_'. $theme, $new_css);
|
| 92 |
}
|
| 93 |
if ($altered) {
|
| 94 |
$vars['styles'] = drupal_get_css($vars['css']);
|
| 95 |
}
|
| 96 |
if (user_access('display CSS')) {
|
| 97 |
if (!empty($overrides)) {
|
| 98 |
drupal_add_js(array('cssapi' => array('css' => $css_map)), 'setting');
|
| 99 |
// $vars['scripts'] = drupal_get_js();
|
| 100 |
}
|
| 101 |
}
|
| 102 |
}
|
| 103 |
|
| 104 |
/**
|
| 105 |
* An implementation of hook_theme_registry_alter()
|
| 106 |
*
|
| 107 |
* Alter the theme registry so that cssapi_preprocess_page runs after theme_preprocess_page.
|
| 108 |
*
|
| 109 |
* @return void
|
| 110 |
**/
|
| 111 |
function cssapi_theme_registry_alter(&$theme_registry) {
|
| 112 |
// we want to run cssapi_preprocess_page AFTER the theme's _preprocess_page
|
| 113 |
if (isset($theme_registry['page'])) {
|
| 114 |
if ($key = array_search('cssapi_preprocess_page', $theme_registry['page']['preprocess functions'])) {
|
| 115 |
unset($theme_registry['page']['preprocess functions'][$key]);
|
| 116 |
}
|
| 117 |
// now tack it on the end
|
| 118 |
$theme_registry['page']['preprocess functions'][] = 'cssapi_preprocess_page';
|
| 119 |
}
|
| 120 |
|
| 121 |
// we're going to also take this opportunity to reset the "cache" of seen CSS files
|
| 122 |
//variable_set('cssapi_css_list_'. $theme, array());
|
| 123 |
}
|
| 124 |
|
| 125 |
// **************************************************************************
|
| 126 |
// UTILITY/API FUNCTIONS **************************************************
|
| 127 |
// **************************************************************************
|
| 128 |
|
| 129 |
// *********************************************
|
| 130 |
// FULL SHEET FUNCTIONS **********************
|
| 131 |
// *********************************************
|
| 132 |
|
| 133 |
function cssapi_all_styles($theme) {
|
| 134 |
return variable_get('cssapi_css_list_'. $theme, array());
|
| 135 |
}
|
| 136 |
|
| 137 |
/**
|
| 138 |
* Save new overrides
|
| 139 |
|
| 140 |
@TODO this function could/should copy styles into files dir
|
| 141 |
|
| 142 |
*
|
| 143 |
* @param $overrides
|
| 144 |
* An array of overrides where the keys are the path to the
|
| 145 |
* non-editable CSS file and the values are the type of override.
|
| 146 |
* example: array('modules/system/system.css' => CSS_REMOVED)
|
| 147 |
* This can also be used to simultaneously 'remove' overrides
|
| 148 |
* by setting to CSS_NORMAL
|
| 149 |
*
|
| 150 |
* @param $theme
|
| 151 |
* The name of the theme being affected
|
| 152 |
*
|
| 153 |
* @return
|
| 154 |
* The new overrides
|
| 155 |
*/
|
| 156 |
function cssapi_save_overrides($overrides, $theme) {
|
| 157 |
$current = variable_get('cssapi_overrides_'. $theme, array());
|
| 158 |
// merge in the new overrides
|
| 159 |
$new = array_merge($current, $overrides);
|
| 160 |
variable_set('cssapi_overrides_'. $theme, $new);
|
| 161 |
return $new;
|
| 162 |
}
|
| 163 |
|
| 164 |
/**
|
| 165 |
* Remove overrides
|
| 166 |
*
|
| 167 |
* @param $overrides
|
| 168 |
* An array of paths to non-editable CSS files which will
|
| 169 |
* no longer be overridden.
|
| 170 |
* example: array('modules/system/system.css')
|
| 171 |
*
|
| 172 |
* @param $theme
|
| 173 |
* The name of the the theme being affected
|
| 174 |
*/
|
| 175 |
function cssapi_remove_overrides($overrides, $theme) {
|
| 176 |
$current = variable_get('cssapi_overrides_'. $theme, array());
|
| 177 |
if (is_array($overrides)) {
|
| 178 |
foreach($overrides as $override) {
|
| 179 |
unset($current[$override]);
|
| 180 |
}
|
| 181 |
}
|
| 182 |
variable_set('cssapi_overrides_'. $theme, $current);
|
| 183 |
}
|
| 184 |
|
| 185 |
function cssapi_load_overrides($theme) {
|
| 186 |
return variable_get('cssapi_overrides_'. $theme, array());
|
| 187 |
}
|
| 188 |
|
| 189 |
/**
|
| 190 |
* Save new additions
|
| 191 |
*
|
| 192 |
* @param $additions
|
| 193 |
* An array of additions where the keys are the unique name
|
| 194 |
* of the override and the values are the path to the CSS
|
| 195 |
* THIS IS DIFFERENT THAN THE OVERRIDES FORMAT
|
| 196 |
* example: array('my_addition' => 'default/files/dcss/bd9rlitbl84.css')
|
| 197 |
* This can also be used to simultaneously 'remove' overrides
|
| 198 |
* by setting to CSS_NORMAL
|
| 199 |
*
|
| 200 |
* @param $theme
|
| 201 |
* The name of the theme being affected
|
| 202 |
*
|
| 203 |
* @return
|
| 204 |
* The new overrides
|
| 205 |
*/
|
| 206 |
function cssapi_save_additions($additions, $theme) {
|
| 207 |
$current = variable_get('cssapi_additions_'. $theme, array());
|
| 208 |
$new = array_merge($current, $additions);
|
| 209 |
variable_set('cssapi_additions_' . $theme, $new);
|
| 210 |
return $new;
|
| 211 |
}
|
| 212 |
|
| 213 |
function cssapi_remove_additions($additions, $theme) {
|
| 214 |
$current = variable_get('cssapi_additions_'. $theme, array());
|
| 215 |
if (is_array($overrides)) {
|
| 216 |
foreach($additions as $addition) {
|
| 217 |
unset($current[$addition]);
|
| 218 |
}
|
| 219 |
}
|
| 220 |
variable_set('cssapi_additions_'. $theme, $current);
|
| 221 |
}
|
| 222 |
|
| 223 |
function cssapi_load_additions($theme) {
|
| 224 |
return variable_get('cssapi_additions_'. $theme, array());
|
| 225 |
}
|
| 226 |
|
| 227 |
|
| 228 |
/**
|
| 229 |
* Save a style sheet
|
| 230 |
*
|
| 231 |
* @param $content The content of the style sheet to be saved
|
| 232 |
* @param $sheet The md5 hashed name of the style sheet - example: md5($theme .'/'. $path_to_orig_sheet)
|
| 233 |
*/
|
| 234 |
function cssapi_save_sheet($content, $sheet) {
|
| 235 |
// Create/confirm dcss/ within the files folder.
|
| 236 |
$cssdir = file_create_path(variable_get('cssapi_dir', 'dcss'));
|
| 237 |
file_check_directory($cssdir, FILE_CREATE_DIRECTORY);
|
| 238 |
|
| 239 |
// save the file
|
| 240 |
file_save_data($content, $sheet, FILE_EXISTS_REPLACE);
|
| 241 |
// save to the database
|
| 242 |
$revision->content = $content;
|
| 243 |
$revision->sheet = $sheet;
|
| 244 |
$revision->timestamp = time();
|
| 245 |
drupal_write_record('cssapi', $revision);
|
| 246 |
// flush the css cache
|
| 247 |
_drupal_flush_css_js();
|
| 248 |
}
|
| 249 |
|
| 250 |
/**
|
| 251 |
* Get the content of a stylesheet from the database
|
| 252 |
*
|
| 253 |
* @param $sheet The encoded name of a stylesheet
|
| 254 |
* @param $vid The version id to load, optional. If omitted, will load latest. (redundant?)
|
| 255 |
*/
|
| 256 |
function cssapi_load_sheet($sheet, $vid = 0) {
|
| 257 |
if ($vid) {
|
| 258 |
$return = db_result(db_query("SELECT content FROM {cssapi} WHERE sheet = '%s' AND vid = %d"), $sheet, $vid);
|
| 259 |
}
|
| 260 |
else {
|
| 261 |
$return = db_result(db_query("SELECT content FROM {cssapi} WHERE sheet = '%s' ORDER BY timestamp DESC"), $sheet);
|
| 262 |
}
|
| 263 |
return $return;
|
| 264 |
}
|
| 265 |
|
| 266 |
function cssapi_load_sheet_revision($vid) {
|
| 267 |
return db_fetch_object(db_query('SELECT content, sheet, timestamp FROM {cssapi} WHERE vid = %d', $vid));
|
| 268 |
}
|
| 269 |
|
| 270 |
/**
|
| 271 |
* Given a non-editable CSS file, return the path to the editable CSS file
|
| 272 |
*
|
| 273 |
* Path scheme:
|
| 274 |
* {files_dir}/{variable_get('cssapi_dir', 'dcss')}/{md5(theme_name.path_to_file)}
|
| 275 |
*
|
| 276 |
* Path examples:
|
| 277 |
* files/dgnr/garland/modules/system/system.css
|
| 278 |
* files/dgnr/pushbutton/themes/pushbutton/style.css
|
| 279 |
* sites/default/files/dgnr/zen_classic/sites/all/modules/fivestar/widgets/flames/flames.css
|
| 280 |
*
|
| 281 |
* @param $path
|
| 282 |
* path to the non-editable CSS file
|
| 283 |
* @param $theme
|
| 284 |
* the name of the theme. this is prefixed in the md5 value to avoid conflicts
|
| 285 |
* between different themes overriding the same files.
|
| 286 |
* if you'd like to have further granularity with perhaps multiple "styles"
|
| 287 |
* per theme, you could append another key to this value - something like "zen/plain"
|
| 288 |
* @param $check_exists
|
| 289 |
* if set to TRUE, the function will check to see if the file exists and return
|
| 290 |
* FALSE if not found
|
| 291 |
*
|
| 292 |
* @return
|
| 293 |
* The path to the editable CSS file. If $check_exists is TRUE, the function
|
| 294 |
* will return boolean FALSE if the file doesn't exist (the path if it does).
|
| 295 |
*/
|
| 296 |
function cssapi_get_editable_path($path, $theme, $check_exists = FALSE) {
|
| 297 |
// hmmm... it sure would be nice if we kept these files named the same as the original
|
| 298 |
$filename = md5($theme .'/'. $path) .'.css';
|
| 299 |
$path = file_directory_path() .'/'. variable_get('cssapi_dir', 'dcss') .'/'. $filename;
|
| 300 |
if ($check_exists && !file_exists($path)) {
|
| 301 |
$path = FALSE;
|
| 302 |
}
|
| 303 |
return $path;
|
| 304 |
}
|
| 305 |
|
| 306 |
/**
|
| 307 |
* Get just the name of a CSS file from a path
|
| 308 |
*/
|
| 309 |
function cssapi_get_short_name($path) {
|
| 310 |
// @TODO change this to use basename()
|
| 311 |
preg_match('/([^\/]*?\.css)/i', $path, $matches);
|
| 312 |
return $matches[0];
|
| 313 |
}
|
| 314 |
|
| 315 |
// *********************************************
|
| 316 |
// RULE EDITING FUNCTIONS ******************** (incomplete)
|
| 317 |
// *********************************************
|
| 318 |
|
| 319 |
/**
|
| 320 |
* API-type function to return the entire CSS declaration
|
| 321 |
*
|
| 322 |
* @param $sheet Complete path to the style sheet.
|
| 323 |
* @param $rule The selectors associated with this declaration.
|
| 324 |
* @return the entire matches array from preg_match()
|
| 325 |
*/
|
| 326 |
function cssapi_get_rule($sheet, $rule) {
|
| 327 |
$sheet = _cssapi_get_path($sheet);
|
| 328 |
// load the contents of the style sheet
|
| 329 |
$contents = drupal_load_stylesheet($sheet, FALSE);
|
| 330 |
|
| 331 |
// find this rule declaration within the stylesheet content
|
| 332 |
// create the regex
|
| 333 |
// replace "," with ",\s*" and stick this into a larger regex
|
| 334 |
$regex = '/\s*'. preg_replace('/,[\s$]*/s', ',[\s$]*', preg_quote($rule, '/')) .'[\s$]*?\{[\s$]*?(\w[\w\W$]*?)[\s$]*?\}/';
|
| 335 |
// run the regex and store results in $match
|
| 336 |
preg_match($regex, $contents, $matches);
|
| 337 |
return $matches;
|
| 338 |
}
|
| 339 |
|
| 340 |
function _cssapi_get_path($sheet) {
|
| 341 |
// some JS/Ajax scripts may send the whole CSS URL, so we convert this to a Drupal path
|
| 342 |
if (strpos($sheet, 'http:') === 0 || strpos($sheet, 'https:') === 0) {
|
| 343 |
// Sheet is a full URL. Convert this to a Drupal path.
|
| 344 |
$parsed = parse_url($sheet);
|
| 345 |
$regex = '/'. preg_quote(base_path(), '/') .'/';
|
| 346 |
$sheet = preg_replace($regex, '', $parsed['path'], 1);
|
| 347 |
}
|
| 348 |
return $sheet;
|
| 349 |
}
|