diff --git a/advagg.module b/advagg.module index 945ebb7..022ee1a 100644 --- a/advagg.module +++ b/advagg.module @@ -761,6 +761,11 @@ function advagg_enabled() { * array('variables' => array(...), 'hooks' => array(...)) */ function advagg_current_hooks_hash_array() { + $aggregate_settings = &drupal_static(__FUNCTION__); + if (isset($aggregate_settings)) { + return $aggregate_settings; + } + // Put all enabled hooks and settings into a big array. $aggregate_settings = array( 'variables' => array( @@ -896,8 +901,8 @@ function advagg_get_hooks_hash_settings($hash) { * ) */ function advagg_get_root_files_dir() { - static $css_paths; - static $js_paths; + $css_paths = &drupal_static(__FUNCTION__ . '_css'); + $js_paths = &drupal_static(__FUNCTION__ . '_js'); // Make sure directories are available and writable. if (empty($css_paths) || empty($js_paths)) { diff --git a/advagg_mod/advagg_mod.admin.inc b/advagg_mod/advagg_mod.admin.inc index b7b2f84..06a8eb4 100644 --- a/advagg_mod/advagg_mod.admin.inc +++ b/advagg_mod/advagg_mod.admin.inc @@ -45,6 +45,54 @@ function advagg_mod_admin_settings_form() { '#default_value' => variable_get('advagg_mod_css_preprocess', ADVAGG_MOD_CSS_PREPROCESS), ); + $form['landing_page'] = array( + '#type' => 'fieldset', + '#title' => t('Inline CSS/JS on specific pages'), + ); + + // Taken from block_admin_configure(). + $access = user_access('use PHP for settings'); + $visibility = variable_get('advagg_mod_inline_visibility', BLOCK_VISIBILITY_LISTED); + $pages = variable_get('advagg_mod_inline_pages', ''); + if ($visibility == BLOCK_VISIBILITY_PHP && !$access) { + $form['landing_page']['path']['visibility'] = array( + '#type' => 'value', + '#value' => $visibility, + ); + $form['landing_page']['path']['pages'] = array( + '#type' => 'value', + '#value' => $pages, + ); + } + else { + $options = array( +// BLOCK_VISIBILITY_NOTLISTED => t('All pages except those listed'), + BLOCK_VISIBILITY_LISTED => t('Only the listed pages'), + ); + $description = t("Specify pages by using their paths. Enter one path per line. The '*' character is a wildcard. Example paths are %blog for the blog page and %blog-wildcard for every personal blog. %front is the front page.", array('%blog' => 'blog', '%blog-wildcard' => 'blog/*', '%front' => '')); + + if (module_exists('php') && $access) { + $options += array(BLOCK_VISIBILITY_PHP => t('Pages on which this PHP code returns TRUE (experts only)')); + $title = t('Pages or PHP code'); + $description .= ' ' . t('If the PHP option is chosen, enter PHP code between %php. Note that executing incorrect PHP code can break your Drupal site.', array('%php' => '')); + } + else { + $title = t('Pages'); + } + $form['landing_page']['path']['advagg_mod_inline_settings'] = array( + '#type' => 'radios', + '#title' => t('Inline CSS/JS on specific pages'), + '#options' => $options, + '#default_value' => $visibility, + ); + $form['landing_page']['path']['advagg_mod_inline_pages'] = array( + '#type' => 'textarea', + '#title' => '' . $title . '', + '#default_value' => $pages, + '#description' => $description, + ); + } + // Clear the cache bins on submit. $form['#submit'][] = 'advagg_mod_admin_settings_form_submit'; diff --git a/advagg_mod/advagg_mod.module b/advagg_mod/advagg_mod.module index 79e41f8..7462113 100644 --- a/advagg_mod/advagg_mod.module +++ b/advagg_mod/advagg_mod.module @@ -60,6 +60,13 @@ function advagg_mod_js_alter(&$js) { } } + // Do not use preprocessing if JS is inlined. + // Do not use defer if JS is inlined. + if (advagg_mod_inline_page()) { + advagg_mod_inline_js($js); + return; + } + // Force all JS to be preprocessed. if (variable_get('advagg_mod_js_preprocess', ADVAGG_MOD_JS_PREPROCESS)) { foreach ($js as $name => &$values) { @@ -105,6 +112,12 @@ function advagg_mod_js_alter(&$js) { * Implements hook_css_alter(). */ function advagg_mod_css_alter(&$css) { + // Do not use preprocessing if CSS is inlined. + if (advagg_mod_inline_page()) { + advagg_mod_inline_css($css); + return; + } + // Force all CSS to be preprocessed. if (variable_get('advagg_mod_css_preprocess', ADVAGG_MOD_CSS_PREPROCESS)) { foreach ($css as $name => &$values) { @@ -118,6 +131,11 @@ function advagg_mod_css_alter(&$css) { * Implements hook_advagg_modify_js_pre_render_alter(). */ function advagg_mod_advagg_modify_js_pre_render_alter(&$children, &$elements) { + // Do not use defer if JS is inlined. + if (advagg_mod_inline_page()) { + return; + } + if (variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER)) { foreach ($children as &$values) { $values['#attributes']['defer'] = TRUE; @@ -145,3 +163,140 @@ function advagg_mod_advagg_modify_js_pre_render_alter(&$children, &$elements) { } } } + +/** + * Returns TRUE if this page should have inline CSS/JS. + */ +function advagg_mod_inline_page() { + $visibility = variable_get('advagg_mod_inline_visibility', BLOCK_VISIBILITY_LISTED); + $pages = variable_get('advagg_mod_inline_pages', ''); + return advagg_mod_match_path($pages, $visibility); +} + +/** + * Transforms all JS files into inline JS. + * + * @param $js + * JS array. + */ +function advagg_mod_inline_js(&$js) { + $aggregate_settings = advagg_current_hooks_hash_array(); + + foreach ($js as $name => &$values) { + // Only process files. + if ($values['type'] != 'file') { + continue; + } + $filename = $values['data']; + if (file_exists($filename)) { + $contents = file_get_contents($filename); + } + // Allow other modules to modify this files contents. + // Call hook_advagg_get_js_file_contents_alter(). + drupal_alter('advagg_get_js_file_contents', $contents, $filename, $aggregate_settings); + + $values['data'] = $contents; + $values['type'] = 'inline'; + } +} + +/** + * Transforms all CSS files into inline CSS. + * + * @param $css + * CSS array. + * + * @see advagg_get_css_aggregate_contents() + * @see drupal_build_css_cache() + */ +function advagg_mod_inline_css(&$css) { + $aggregate_settings = advagg_current_hooks_hash_array(); + $optimize = TRUE; + foreach ($css as $name => &$values) { + // Only process files. + if ($values['type'] != 'file') { + continue; + } + + $file = $values['data']; + if (file_exists($file)) { + $contents = drupal_load_stylesheet($file, $optimize); + + // Build the base URL of this CSS file: start with the full URL. + $css_base_url = file_create_url($file); + // Move to the parent. + $css_base_url = substr($css_base_url, 0, strrpos($css_base_url, '/')); + // Simplify to a relative URL if the stylesheet URL starts with the + // base URL of the website. + if (substr($css_base_url, 0, strlen($GLOBALS['base_root'])) == $GLOBALS['base_root']) { + $css_base_url = substr($css_base_url, strlen($GLOBALS['base_root'])); + } + + _drupal_build_css_path(NULL, $css_base_url . '/'); + // Anchor all paths in the CSS with its base URL, ignoring external and absolute paths. + $contents = preg_replace_callback('/url\(\s*[\'"]?(?![a-z]+:|\/+)([^\'")]+)[\'"]?\s*\)/i', '_drupal_build_css_path', $contents); + + // Allow other modules to modify this files contents. + // Call hook_advagg_get_css_file_contents_alter(). + drupal_alter('advagg_get_css_file_contents', $contents, $file, $aggregate_settings); + + // Per the W3C specification at http://www.w3.org/TR/REC-CSS2/cascade.html#at-import, + // @import rules must proceed any other style, so we move those to the top. + $regexp = '/@import[^;]+;/i'; + preg_match_all($regexp, $contents, $matches); + $contents = preg_replace($regexp, '', $contents); + $contents = implode('', $matches[0]) . $contents; + + $values['data'] = $contents; + $values['type'] = 'inline'; + } + } +} + +/** + * Transforms all CSS files into inline CSS. + * + * @param $pages + * string from the advagg_mod_inline_pages variable. + * @param $visibility + * visibility setting from the advagg_mod_inline_visibility variable. + * + * @see block_block_list_alter() + */ +function advagg_mod_match_path($pages, $visibility) { + // Limited visibility blocks must list at least one page. + if ($visibility == BLOCK_VISIBILITY_LISTED && empty($pages)) { + $page_match = FALSE; + } + elseif ($pages) { + // Match path if necessary. + // Convert path to lowercase. This allows comparison of the same path + // with different case. Ex: /Page, /page, /PAGE. + $pages = drupal_strtolower($pages); + if ($visibility < BLOCK_VISIBILITY_PHP) { + // Convert the Drupal path to lowercase + $path = drupal_strtolower(drupal_get_path_alias($_GET['q'])); + // Compare the lowercase internal and lowercase path alias (if any). + $page_match = drupal_match_path($path, $pages); + if ($path != $_GET['q']) { + $page_match = $page_match || drupal_match_path($_GET['q'], $pages); + } + // When $visibility has a value of 0 (BLOCK_VISIBILITY_NOTLISTED), + // the block is displayed on all pages except those listed in $pages. + // When set to 1 (BLOCK_VISIBILITY_LISTED), it is displayed only on those + // pages listed in $block->pages. + $page_match = !($visibility xor $page_match); + } + elseif (module_exists('php')) { + $page_match = php_eval($pages); + } + else { + $page_match = FALSE; + } + } + else { + $page_match = TRUE; + } + + return $page_match; +}