diff --git a/advagg_mod/advagg_mod.admin.inc b/advagg_mod/advagg_mod.admin.inc index fe1477d..a18b065 100644 --- a/advagg_mod/advagg_mod.admin.inc +++ b/advagg_mod/advagg_mod.admin.inc @@ -54,9 +54,61 @@ function advagg_mod_admin_settings_form() { '#description' => module_exists('advagg_bundler') ? t('You might want to increase the CSS Bundles Per Page if this is checked.', array('@link' => url($config_path . '/advagg/bundler'))) : '', ); + $form['remove_files'] = array( + '#type' => 'fieldset', + '#title' => t('Remove CSS/JS files'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + $form['remove_files']['instructions'] = array( + '#type' => 'item', + '#title' => t('Instructions'), + '#markup' => t("
Enter one file per line.
The *
character is a wildcard to match all similar items, for instance system/*.css
will remove all CSS provided by the System module; overlay/*.js
will remove all JS provided by the Overlay module.
The ~
character is a reserved character to keep all similar items if they would otherwise be removed, for instance ~system/system.menus.css
to keep System module's menu CSS even if we remove the rest of System module's CSS; ~overlay/overlay-child.js
to keep Overlay module's overlay-child JS even if we remove the rest of Overlay module's JS.
You may use
:all
to target all CSS/JS files
:core
to target all Core provided CSS/JS files
:contrib
to target all Contrib provided CSS/JS files
:base-theme
to target all base theme provided CSS/JS files
:current-theme
to target all CSS/JS files provided by the current theme."),
+ );
+ $list_themes = list_themes();
+ foreach ($list_themes as $values) {
+ $form['remove_files'][$values->name] = array(
+ '#type' => 'fieldset',
+ '#title' => t('@name - @version', array(
+ '@name' => $values->info['name'],
+ '@version' => $values->info['version'],
+ )),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ );
+ $form['remove_files'][$values->name]['theme_info'] = array(
+ '#type' => 'item',
+ '#title' => $values->status ? t('Enabled') : t('Disabled') ,
+ '#markup' => t('@filename
@description', array(
+ '@description' => $values->info['description'],
+ '@filename' => $values->filename,
+ )),
+ );
+ $form['remove_files'][$values->name]['advagg_mod_css_excludes_' . $values->name] = array(
+ '#type' => 'textarea',
+ '#title' => t('Remove CSS files'),
+ '#default_value' => variable_get('advagg_mod_css_excludes_' . $values->name, ''),
+ );
+ $form['remove_files'][$values->name]['advagg_mod_css_excludes_' . $values->name . '_regex'] = array(
+ '#type' => 'hidden',
+ '#default_value' => variable_get('advagg_mod_css_excludes_' . $values->name . '_regex', array()),
+ );
+ $form['remove_files'][$values->name]['advagg_mod_js_excludes_' . $values->name] = array(
+ '#type' => 'textarea',
+ '#title' => t('Remove JS files'),
+ '#default_value' => variable_get('advagg_mod_js_excludes_' . $values->name, ''),
+ );
+ $form['remove_files'][$values->name]['advagg_mod_js_excludes_' . $values->name . '_regex'] = array(
+ '#type' => 'hidden',
+ '#default_value' => variable_get('advagg_mod_js_excludes_' . $values->name . '_regex', array()),
+ );
+ }
+
$form['landing_page'] = array(
'#type' => 'fieldset',
'#title' => t('Inline CSS/JS on specific pages'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
);
// Taken from block_admin_configure().
$access = user_access('use PHP for settings');
@@ -113,6 +165,8 @@ function advagg_mod_admin_settings_form() {
'#maxlength' => 128,
);
+ // Set excludes values.
+ $form['#submit'][] = 'advagg_mod_admin_settings_form_submit_excludes';
// Clear the cache bins on submit.
$form['#submit'][] = 'advagg_mod_admin_settings_form_submit';
@@ -129,3 +183,199 @@ function advagg_mod_admin_settings_form_submit($form, &$form_state) {
cache_clear_all('*', $bin, TRUE);
}
}
+
+/**
+ * Submit handler for the theme settings form.
+ *
+ * This form will take the theme settings, and for the css and js excludes,
+ * create the regex that will be required to remove the css and js files at
+ * will. It will then selectively clear the caches for those specific cache
+ * items.
+ */
+function advagg_mod_admin_settings_form_submit_excludes($form, &$form_state) {
+ // Pull out the values we care about.
+ $exclude_list = array();
+ foreach ($form_state['values'] as $key => $value) {
+ // Reset for this run.
+ $theme_name = '';
+ $type = '';
+ $saved_value = '';
+
+ // Extract info.
+ if (strpos($key, 'advagg_mod_css_excludes_') === 0 && strpos($key, '_regex') === FALSE) {
+ $theme_name = substr($key, strlen('advagg_mod_css_excludes_'));
+ $type = 'css';
+ $saved_value = variable_get('advagg_mod_css_excludes_' . $theme_name, '');
+ }
+ elseif (strpos($key, 'advagg_mod_js_excludes_') === 0 && strpos($key, '_regex') === FALSE) {
+ $theme_name = substr($key, strlen('advagg_mod_js_excludes_'));
+ $type = 'js';
+ $saved_value = variable_get('advagg_mod_js_excludes_' . $theme_name, '');
+ }
+
+ // Skip if not the right setting.
+ if (empty($theme_name)) {
+ continue;
+ }
+
+ // Explode and trim the values for the exclusion rules.
+ $value = implode("\n", array_filter(array_map('trim', explode("\n", $value))));
+
+ // Skip if nothing to do.
+ if ($saved_value === $value) {
+ continue;
+ }
+
+ $excludes = array();
+ if (!empty($value)) {
+ $excludes = explode("\n", $value);
+ }
+ if (!empty($excludes)) {
+ // Now we get the regex and set that.
+ $excludes = advagg_mod_generate_exclude_full($excludes);
+
+ $excludes['exclude'] = advagg_mod_generate_path_regex($excludes['exclude']);
+ $excludes['include'] = advagg_mod_generate_path_regex($excludes['include']);
+
+ if ($type = 'css') {
+ // Make sure that RTL styles are excluded as well when a file name has been
+ // specified with it's full .css file extension.
+ $excludes['exclude'] = preg_replace('/\\\.css$/', '(\.css|-rtl\.css)', $excludes['exclude']);
+ $excludes['include'] = preg_replace('/\\\.css$/', '(\.css|-rtl\.css)', $excludes['include']);
+ }
+
+ // Last check, if we didnt actually have anything in excludes, we don't
+ // save a thing.
+ if ($excludes['exclude'] == FALSE) {
+ $excludes = array();
+ }
+ }
+ $form_state['values'][$key . '_regex'] = $excludes;
+ }
+}
+
+/**
+ * Helper function to change magic keywords into the exclude array.
+ *
+ * This helper function will remove kewords such as :contrib or :base-theme and
+ * change them into the paths that they represent.
+ *
+ * @param $exclude array
+ * An array of paths to remove.
+ *
+ * @return array
+ * An array with 'exclude' and 'include' as the keys. Exclude contains a set
+ * of files to remove, while 'include' has the files to keep.
+ */
+function advagg_mod_generate_exclude_full($array) {
+ global $base_theme_info, $theme_info;
+
+ $return = array(
+ 'exclude' => array(),
+ 'include' => array(),
+ );
+
+ foreach ($array as $item) {
+ $invert = substr($item, 0, 1) == '~' ? TRUE : FALSE;
+
+ if ($invert) {
+ $item = substr($item, 1);
+ }
+
+ $items = array();
+
+ // We now check the string against a set of standard variables.
+ if ($item == ':all') {
+ $items[] = '*';
+ }
+ elseif ($item == ':core') {
+ // This is unique as there are several areas where core files might be.
+ $items = array(
+ 'misc/*',
+ 'modules/*',
+ 'themes/*',
+ );
+ }
+ elseif ($item == ':contrib') {
+ $items[] = 'sites/all/modules/*';
+ }
+ elseif ($item == ':base-theme') {
+ if (empty($base_theme_info)) {
+ // We do not actually have a base theme.
+ continue ;
+ }
+
+ $items[] = drupal_get_path('theme', $base_theme_info[0]->name) . '/*';
+ }
+ elseif ($item == ':current-theme') {
+ $items[] = drupal_get_path('theme', $theme_info->name) . '/*';
+ }
+ else {
+ $items[] = $item;
+ }
+
+ if ($invert) {
+ $return['include'] = array_merge($return['include'], $items);
+ }
+ else {
+ $return['exclude'] = array_merge($return['exclude'], $items);
+ }
+ }
+
+
+ return $return;
+}
+
+/**
+ * Helper function for generating a regex from a list of paths.
+ *
+ * Generates a single regex from a list of file paths that can be used to match
+ * JS or CSS files using preg_grep() for example in hook_css_alter() or
+ * hook_js_alter(). The '*' (asterisk) character can be used as a wild-card.
+ *
+ * @param $paths
+ * An array of file paths.
+ *
+ * @return string
+ * The generated regex.
+ *
+ * @see hook_js_alter()
+ * @see hook_css_alter()
+ */
+function advagg_mod_generate_path_regex($paths) {
+ foreach ($paths as &$item) {
+ // The first segment (everything before the first slash) is the namespace.
+ // This rule only applies to local files... So if the namespace can not be
+ // mapped to a module, profile or theme engine we assume that the we are
+ // trying to target an external file.
+ list($namespace) = explode('/', $item);
+
+ // Check if the namespace refers to a file residing in the 'misc' folder.
+ if ($namespace != '*') {
+ if ($namespace == 'misc') {
+ $prefix = DRUPAL_ROOT . '/misc';
+ $item = substr_replace($item, $prefix, 0, strlen($namespace));
+ }
+ else {
+ // Otherwise, check if it refers to a theme, module, profile or theme
+ // engine.
+ foreach (array('theme', 'module', 'profile', 'theme_engine') as $type) {
+ // We can't use drupal_get_path() directly because that uses dirname()
+ // internally which returns '.' if no filename was found.
+ if ($filename = drupal_get_filename($type, $namespace)) {
+ $prefix = dirname($filename);
+ $item = substr_replace($item, $prefix, 0, strlen($namespace));
+ break;
+ }
+ }
+ }
+ }
+
+ // Escape any regex characters and turn asterisk wildcards into actual regex
+ // wildcards.
+ $item = preg_quote($item, '/');
+ $item = str_replace('\*', '(.*)', $item);
+ }
+
+ return empty($paths) ? FALSE : '/^(' . implode('|', $paths) . ')$/';
+}
diff --git a/advagg_mod/advagg_mod.module b/advagg_mod/advagg_mod.module
index 5c7efe6..857e89a 100644
--- a/advagg_mod/advagg_mod.module
+++ b/advagg_mod/advagg_mod.module
@@ -91,6 +91,9 @@ function advagg_mod_menu() {
* Implements hook_js_alter().
*/
function advagg_mod_js_alter(&$js) {
+ // Remove any JS files if configured to do to.
+ advagg_mod_remove_files($js, 'js');
+
// Move all JS to the footer.
$move_js_to_footer = variable_get('advagg_mod_js_footer', ADVAGG_MOD_JS_FOOTER);
if (!empty($move_js_to_footer)) {
@@ -162,6 +165,9 @@ function advagg_mod_js_alter(&$js) {
* Implements hook_css_alter().
*/
function advagg_mod_css_alter(&$css) {
+ // Remove any CSS files if configured to do to.
+ advagg_mod_remove_files($css, 'css');
+
// Do not use preprocessing if CSS is inlined.
if (advagg_mod_inline_page()) {
advagg_mod_inline_css($css);
@@ -361,3 +367,92 @@ function advagg_mod_match_path($pages, $visibility) {
return $page_match;
}
+
+/**
+ * Helper function to remove unwanted css or js.
+ *
+ * @param array $data
+ * The CSS or JS array.
+ * @param string $type
+ * Either 'css' or 'js' depending on the file array.
+ */
+function advagg_mod_remove_files(&$data, $type) {
+ // First check to see if we are even going to exclude anything.
+ $excludes = variable_get('advagg_mod_' . $type . '_excludes_' . $GLOBALS['theme_key'] . '_regex', array());
+ if (empty($excludes) || empty($excludes['exclude'])) {
+ return;
+ }
+
+ // Using regex, remove files from the CSS/JS array.
+ advagg_mod_exclude_assets($data, $excludes['exclude'], $excludes['include']);
+}
+
+/**
+ * Helper function for eliminating elements from an array using a simplified
+ * regex pattern.
+ *
+ * @param $elements
+ * The array of elements that should have some of its items removed.
+ * @param $regex
+ * A regex as generated by omega_generate_path_regex().
+ */
+function advagg_mod_exclude_assets(&$elements, $exclude, $include) {
+ $mapping = advagg_mod_generate_asset_mapping($elements);
+
+ // We first check to see if we have an include array. If not, we don't need to
+ // check it as well.
+ if (!empty($include)) {
+ // We do a grep on each the exclude list, and include list and return the keys
+ // of the exclude list minus those that are specifically included.
+
+ $full_exclude = array_diff_key(preg_grep($exclude, $mapping), preg_grep($include, $mapping));
+
+ // Finally, implode the array of items to exclude into a proper regex and
+ // invoke in on the array of files to be excluded.
+ $elements = array_diff_key($elements, $full_exclude);
+ }
+ else {
+ // We only have an exclude array, so we only have to do a preg_grep for it.
+ $elements = array_diff_key($elements, preg_grep($exclude, $mapping));
+ }
+}
+
+/**
+ * Helper function for generating a map of assets based on the data attribute.
+ *
+ * We can not rely on the array keys of the JS and CSS file arrays in Drupal
+ * because in case of inline JS or CSS (which uses numerical array keys) and due
+ * to potential overrides of the 'data' attribute which holds the actual,
+ * reliable path of the file. This function returns a single-level array of
+ * reliable JS/CSS file paths using the original array keys as keys. Elements of
+ * type 'inline' or 'setting' are ignored.
+ *
+ * @param $elements
+ * An array of JS or CSS files as given in hook_css_alter() or
+ * hook_js_alter().
+ *
+ * @return array
+ * A map of file paths generated from $elements.
+ *
+ * @see hook_js_alter()
+ * @see hook_css_alter()
+ */
+function advagg_mod_generate_asset_mapping($elements) {
+ $mapping = array();
+ foreach ($elements as $key => $item) {
+ if ($item['type'] == 'inline' || $item['type'] == 'setting') {
+ // in-line CSS/JS is not supported.
+ continue;
+ }
+
+ // We need to build an array containing just the 'data' attribute because
+ // that's the actual path of the file. The array key of the elements can
+ // be something else if someone is sneaky enough to use drupal_add_js() or
+ // drupal_add_css() with a bogus first argument (normally, that is the
+ // path to the file) and then specify the actual path through the 'data'
+ // attribute in the $options array.
+ $mapping[$key] = $item['data'];
+ }
+
+ return $mapping;
+}