From 765ae9379f2cf753aab1e038ed2f4363b58d8e79 Mon Sep 17 00:00:00 2001 From: Guillaume Viguier Date: Mon, 4 Jul 2016 13:05:56 +0200 Subject: [PATCH 1/6] Updated advagg to 2.18 --- sites/all/modules/contrib/advagg/README.txt | 236 ++++-- .../modules/contrib/advagg/advagg.admin.inc | 168 ++++- .../modules/contrib/advagg/advagg.admin.js | 18 +- .../modules/contrib/advagg/advagg.advagg.inc | 58 +- .../modules/contrib/advagg/advagg.cache.inc | 197 ++++- .../modules/contrib/advagg/advagg.drush.inc | 56 +- sites/all/modules/contrib/advagg/advagg.inc | 242 +++++- sites/all/modules/contrib/advagg/advagg.info | 6 +- .../all/modules/contrib/advagg/advagg.install | 690 ++++++++++++++---- .../modules/contrib/advagg/advagg.missing.inc | 369 ++++++++-- .../all/modules/contrib/advagg/advagg.module | 460 ++++++++++-- .../advagg_bundler/advagg_bundler.admin.inc | 22 +- .../advagg_bundler/advagg_bundler.advagg.inc | 16 +- .../advagg/advagg_bundler/advagg_bundler.info | 6 +- .../advagg_bundler/advagg_bundler.module | 361 +++++---- .../advagg/advagg_css_cdn/advagg_css_cdn.info | 6 +- .../advagg_css_cdn/advagg_css_cdn.module | 5 +- .../advagg_css_compress.admin.inc | 13 +- .../advagg_css_compress.info | 6 +- .../advagg/advagg_css_compress/yui/CSSMin.inc | 31 +- .../advagg_ext_compress.info | 6 +- .../advagg_ext_compress.module | 8 +- .../advagg/advagg_font/advagg_font.admin.inc | 83 ++- .../advagg/advagg_font/advagg_font.advagg.inc | 22 +- .../advagg/advagg_font/advagg_font.info | 6 +- .../advagg/advagg_font/advagg_font.install | 78 ++ .../contrib/advagg/advagg_font/advagg_font.js | 28 +- .../advagg/advagg_font/advagg_font.module | 149 +++- .../advagg/advagg_js_cdn/advagg_js_cdn.info | 6 +- .../advagg/advagg_js_cdn/advagg_js_cdn.module | 2 +- .../advagg/advagg_js_cdn/js/jquery-ui.js | 5 + .../advagg_js_compress.admin.inc | 27 +- .../advagg_js_compress.advagg.inc | 61 +- .../advagg_js_compress.info | 6 +- .../advagg_js_compress.module | 3 +- .../advagg_js_compress.php53.inc | 21 +- .../advagg/advagg_js_compress/jsqueeze.inc | 93 ++- .../advagg/advagg_mod/advagg_mod.admin.inc | 103 ++- .../contrib/advagg/advagg_mod/advagg_mod.info | 6 +- .../advagg/advagg_mod/advagg_mod.module | 672 ++++++++++++++--- .../advagg/advagg_mod/advagg_mod_css_defer.js | 2 +- .../advagg/advagg_mod/cssrelpreload.js | 50 ++ .../advagg/advagg_mod/cssrelpreload.min.js | 2 + .../contrib/advagg/advagg_mod/loadCSS.js | 114 +-- .../contrib/advagg/advagg_mod/loadCSS.min.js | 4 +- .../advagg/advagg_sri/advagg_sri.admin.inc | 53 ++ .../advagg/advagg_sri/advagg_sri.advagg.inc | 234 ++++++ .../contrib/advagg/advagg_sri/advagg_sri.info | 14 + .../advagg/advagg_sri/advagg_sri.install | 67 ++ .../advagg/advagg_sri/advagg_sri.module | 64 ++ .../advagg_validator.admin.inc | 6 +- .../advagg_validator/advagg_validator.inc | 2 +- .../advagg_validator/advagg_validator.info | 6 +- .../advagg_validator/advagg_validator.install | 5 +- .../advagg_validator/advagg_validator.js | 8 +- .../modules/contrib/advagg/tests/advagg.test | 171 ++++- .../advagg/tests/css_test_files/advagg.css | 16 + .../css_test_files/advagg.css.optimized.css | 2 +- 58 files changed, 4248 insertions(+), 923 deletions(-) create mode 100644 sites/all/modules/contrib/advagg/advagg_font/advagg_font.install create mode 100644 sites/all/modules/contrib/advagg/advagg_mod/cssrelpreload.js create mode 100644 sites/all/modules/contrib/advagg/advagg_mod/cssrelpreload.min.js create mode 100644 sites/all/modules/contrib/advagg/advagg_sri/advagg_sri.admin.inc create mode 100644 sites/all/modules/contrib/advagg/advagg_sri/advagg_sri.advagg.inc create mode 100644 sites/all/modules/contrib/advagg/advagg_sri/advagg_sri.info create mode 100644 sites/all/modules/contrib/advagg/advagg_sri/advagg_sri.install create mode 100644 sites/all/modules/contrib/advagg/advagg_sri/advagg_sri.module diff --git a/sites/all/modules/contrib/advagg/README.txt b/sites/all/modules/contrib/advagg/README.txt index c5c8680d1..75954f510 100644 --- a/sites/all/modules/contrib/advagg/README.txt +++ b/sites/all/modules/contrib/advagg/README.txt @@ -9,10 +9,12 @@ CONTENTS OF THIS FILE - Features & benefits - Configuration + - Additional options for `drupal_add_css/js` functions - JSMin PHP Extension - JavaScript Bookmarklet - Technical Details & Hooks - How to get a high PageSpeed score + - Settings that Drupal.org uses - nginx Configuration - Troubleshooting @@ -34,10 +36,10 @@ FEATURES & BENEFITS aggregate; if that file has changed then flush the correct caches so the changes go out. The new name ensures changes go out when using CDNs. - One can add JS to any region of the theme & have it aggregated. - - Url query string to turn off aggregation for that request. ?advagg=0 will + - Url query string to turn off aggregation for that request. `?advagg=0` will turn off file aggregation if the user has the "bypass advanced aggregation" - permission. ?advagg=-1 will completely bypass all of Advanced CSS/JS - Aggregations modules and submodules. ?advagg=1 will enable Advanced CSS/JS + permission. `?advagg=-1` will completely bypass all of Advanced CSS/JS + Aggregations modules and submodules. `?advagg=1` will enable Advanced CSS/JS Aggregation if it is currently disabled. - Button on the admin page for dropping a cookie that will turn off file aggregation. Useful for theme development. @@ -46,28 +48,28 @@ FEATURES & BENEFITS **Included submodules** - - advagg_bundler: + - `advagg_bundler`: Smartly groups files together - given a target number of CSS/JS aggregates, this will try very hard to meet that goal. - - advagg_css_cdn: + - `advagg_css_cdn`: Load CSS libraries from a public CDN; currently only supports Google's CDN. - - advagg_css_compress: + - `advagg_css_compress`: Compress the compiled CSS files using a 3rd party compressor; currently supports YUI (included). - - advagg_js_cdn: + - `advagg_js_cdn`: Load JavaScript libraries from a public CDN; currently only supports Google's CDN. - - advagg_js_compress: + - `advagg_js_compress`: Compress the compiled JavaScript files using a 3rd party compressor; currently supports JSMin+ (included). - - advagg_mod: + - `advagg_mod`: Includes additional tweaks that may not work for all sites: - Force preprocessing for all CSS/JS. - Move JS to footer. - Add defer tag to all JS. - Inline all CSS/JS for given paths. - Use a shared directory for a unified multisite. - - advagg_validator: + - `advagg_validator`: Validate all CSS files using jigsaw.w3.org. Check all CSS files with CSSLint. Check all JS files with JSHint. @@ -82,7 +84,7 @@ Settings page is located at: - Enable Advanced Aggregation: Check this to start using this module. You can also quickly disable the module here. For testing purposes, this has the same - effect as placing ?advagg=-1 in the URL. Disabled by default. + effect as placing `?advagg=-1` in the URL. Disabled by default. - Create .gz files: Check this by default as it will improve your performance. For every Aggregated file generated, this will create a gzip version of file and then only serve it out if the browser accepts gzip files compression. @@ -112,9 +114,9 @@ Settings page is located at: with IE9, compatibility mode is forced off if this is enabled by adding this tag in the html head: - + Disabled by default. - Prevent more than 4095 CSS selectors in an aggregated CSS file: Internet @@ -132,7 +134,8 @@ Settings page is located at: located at `admin/config/development/performance/advagg/info`. This page provides debugging information. There are no configuration options here. - - Hook Theme Info: Displays the process_html order. Used for debugging. + + - Hook Theme Info: Displays the `process_html` order. Used for debugging. - CSS files: Displays how often a file has changed. - JS files: Displays how often a file has changed. - Modules implementing AdvAgg CSS/JS hooks: Lets you know what modules are @@ -151,6 +154,7 @@ collection of commands to control the cache and to manage testing of this module. In general this page is useful when troubleshooting some aggregation issues. For normal operations, you do not need to do anything on this page below the Smart Cache Flush. There are no configuration options here. + - Smart Cache Flush - Flush AdvAgg Cache: Scan all files referenced in aggregated files. If any of them have changed, increment the counters containing that file and @@ -159,10 +163,10 @@ the Smart Cache Flush. There are no configuration options here. - Aggregation Bypass Cookie - Toggle The "aggregation bypass cookie" For This Browser: This will set or remove a cookie that disables aggregation for the remainder of the browser - session. It acts almost the same as adding ?advagg=0 to every URL. + session. It acts almost the same as adding `?advagg=0` to every URL. - Cron Maintenance Tasks - - Remove All Stale Files: Scan all files in the advagg_css/js directories and + - Remove All Stale Files: Scan all files in the `advagg_css/js` directories and remove the ones that have not been accessed in the last 30 days. - Clear Missing Files From the Database: Scan for missing files and remove the associated entries in the database. @@ -172,7 +176,7 @@ the Smart Cache Flush. There are no configuration options here. - Drastic Measures - Clear All Caches: Remove all data stored in the advagg cache bins. - - Remove All Generated Files. Remove all files in the advagg_css/js + - Remove All Generated Files. Remove all files in the `advagg_css/js` directories. - Increment Global Counter: Force the creation of all new aggregates by incrementing a global counter. @@ -186,6 +190,9 @@ current defaults are shown. // Display a message that the bypass cookie is set. $conf['advagg_show_bypass_cookie_message'] = TRUE; + // Display a message when a css/js file changed while in development mode. + $conf['advagg_show_file_changed_message'] = TRUE; + // Skip the 404 check on status page. $conf['advagg_skip_404_check'] = FALSE; @@ -249,6 +256,34 @@ current defaults are shown. // Skip preprocess and enabled checks. $conf['advagg_skip_enabled_preprocess_check'] = FALSE; + // Default root dir for the advagg files; see advagg_get_root_files_dir(). + $conf['advagg_root_dir_prefix'] = 'public://'; + + // How long to wait when writing the aggregate if a file is missing or the + // hash doesn't match. + $conf['advagg_file_read_failure_timeout'] = 3600; + + +ADDITIONAL OPTIONS FOR DRUPAL_ADD_CSS/JS FUNCTIONS +-------------------------------------------------- + +AdvAgg extends the available options inside of `drupal_add_css` and +`drupal_add_js`. + +`drupal_add_js` - additional keys for $options. + + - `browsers`: Works the same as the one found in drupal_add_css. + - `onload`: Run this js code when after the js file has loaded. + - `onerror`: Run this js code when if the js file did not load. + - `async`: TRUE - Load this file using async. + - `no_defer`: TRUE - Never defer or async load this js file. + +Both `drupal_add_js` + `drupal_add_css` - additional keys for $options. + + - `scope_lock`: TRUE - Make sure the scope of this will not ever change. + - `movable`: FALSE - Make sure the ordering of this will not ever change. + + JSMIN PHP EXTENSION ------------------- @@ -274,10 +309,10 @@ TECHNICAL DETAILS & HOOKS **Technical Details** - There are five database tables and two cache table used by advagg. - advagg_schema documents what they are used for. + `advagg_schema` documents what they are used for. - Files are generated by this pattern: - css__[BASE64_HASH]__[BASE64_HASH]__[BASE64_HASH].css + css__[BASE64_HASH]__[BASE64_HASH]__[BASE64_HASH].css The first base64 hash value tells us what files are included in the aggregate. Changing what files get included will change this value. @@ -293,23 +328,22 @@ TECHNICAL DETAILS & HOOKS - To trigger scanning of the CSS / JS file cache to identify new files, run the following: - // Trigger reloading the CSS and JS file cache in AdvAgg. - if (module_exists('advagg')) { - module_load_include('inc', 'advagg', 'advagg.cache'); - advagg_push_new_changes(); - } + // Trigger reloading the CSS and JS file cache in AdvAgg. + if (module_exists('advagg')) { + module_load_include('inc', 'advagg', 'advagg.cache'); + advagg_push_new_changes(); + } - Aggressive Cache Setting: This will fully cache the rendered html generated by AdvAgg. The cache ID is set by this code: - $hooks_hash = advagg_get_current_hooks_hash(); - $css_cache_id_full = 'advagg:css:full:' . $hooks_hash . ':' . drupal_hash_base64(serialize($full_css)); - - $hooks_hash = advagg_get_current_hooks_hash(); - $js_cache_id_full = 'advagg:js:full:' . $hooks_hash . ':' . drupal_hash_base64(serialize($js_scope_array)); + $hooks_hash = advagg_get_current_hooks_hash(); + $css_cache_id_full = 'advagg:css:full:' . $hooks_hash . ':' . drupal_hash_base64(serialize($full_css)); + $hooks_hash = advagg_get_current_hooks_hash(); + $js_cache_id_full = 'advagg:js:full:' . $hooks_hash . ':' . drupal_hash_base64(serialize($js_scope_array)); The second and final hash value in this cache id is the css/js_hash value. - This takes the input from drupal_add_css/js() and creates a hash value from + This takes the input from `drupal_add_css/js()` and creates a hash value from it. If a different file is added and/or inline code changed, this hash value will be different. @@ -321,55 +355,58 @@ TECHNICAL DETAILS & HOOKS `admin/config/development/performance/advagg/info` under "Hooks And Variables Used In Hash". An example of this being properly used is if you enable the core locale module the language key will appear in the - array. This is needed because the locale_css_alter and locale_js_alter + array. This is needed because the `locale_css_alter` and `locale_js_alter` functions both use the global $language variable in determining what css or js files need to be altered. To add in your own context you can use - hook_advagg_current_hooks_hash_array_alter to do so. Be careful when doing so - as including something like the user id will make every user have a different - set of aggregate files. + `hook_advagg_current_hooks_hash_array_alter` to do so. Be careful when doing + so as including something like the user id will make every user have a + different set of aggregate files. **Hooks** Modify file contents: - - advagg_get_css_file_contents_alter. Modify the data of each file before it + + - `advagg_get_css_file_contents_alter`. Modify the data of each file before it gets glued together into the bigger aggregate. Useful for minification. - - advagg_get_js_file_contents_alter. Modify the data of each file before it + - `advagg_get_js_file_contents_alter`. Modify the data of each file before it gets glued together into the bigger aggregate. Useful for minification. - - advagg_get_css_aggregate_contents_alter. Modify the data of the complete + - `advagg_get_css_aggregate_contents_alter`. Modify the data of the complete aggregate before it gets written to a file. Useful for minification. - - advagg_get_js_aggregate_contents_alter. Modify the data of the complete + - `advagg_get_js_aggregate_contents_alter`. Modify the data of the complete aggregate before it gets written to a file.Useful for minification. - - advagg_save_aggregate_alter. Modify the data of the complete aggregate + - `advagg_save_aggregate_alter`. Modify the data of the complete aggregate allowing one create multiple files from one base file. Useful for gzip compression. Also useful for mirroring data. Modify file names and aggregate bundles: - - advagg_current_hooks_hash_array_alter. Add in your own settings and hooks + + - `advagg_current_hooks_hash_array_alter`. Add in your own settings and hooks allowing one to modify the 3rd base64 hash in a filename. - - advagg_build_aggregate_plans_alter. Regroup files into different aggregates. - - advagg_css_groups_alter. Allow other modules to modify $css_groups right + - `advagg_build_aggregate_plans_alter`. Regroup files into different aggregates. + - `advagg_css_groups_alter`. Allow other modules to modify `$css_groups` right + before it is processed. + - `advagg_js_groups_alter`. Allow other modules to modify `$js_groups` right before it is processed. - - advagg_js_groups_alter. Allow other modules to modify $js_groups right before - it is processed. Others: - - advagg_hooks_implemented_alter. Tell advagg about other hooks related to + + - `advagg_hooks_implemented_alter`. Tell advagg about other hooks related to advagg. - - advagg_get_root_files_dir_alter. Allow other modules to alter css and js + - `advagg_get_root_files_dir_alter`. Allow other modules to alter css and js paths. - - advagg_modify_css_pre_render_alter. Allow other modules to modify $children + - `advagg_modify_css_pre_render_alter`. Allow other modules to modify $children & $elements before they are rendered. - - advagg_modify_js_pre_render_alter. Allow other modules to modify $children + - `advagg_modify_js_pre_render_alter`. Allow other modules to modify $children & $elements before they are rendered. - - advagg_changed_files. Let other modules know about the changed files. - - advagg_removed_aggregates. Let other modules know about removed aggregates. - - advagg_scan_for_changes. Let other modules see if files related to this file - has changed. Useful for detecting changes to referenced images in css. - - advagg_get_info_on_files_alter. Let other modules modify information about + - `advagg_changed_files`. Let other modules know about the changed files. + - `advagg_removed_aggregates`. Let other modules know about removed aggregates. + - `advagg_scan_for_changes`. Let other modules see if files related to this + file has changed. Useful for detecting changes to referenced images in css. + - `advagg_get_info_on_files_alter`. Let other modules modify information about the base CSS/JS files. - - advagg_context_alter. Allow other modules to swap important contextual + - `advagg_context_alter`. Allow other modules to swap important contextual information on generation. - - advagg_bundler_analysis. If the bundler module is installed allow for other + - `advagg_bundler_analysis`. If the bundler module is installed allow for other modules to change the bundler analysis. @@ -377,19 +414,22 @@ HOW TO GET A HIGH PAGESPEED SCORE --------------------------------- Go to `admin/config/development/performance/advagg` + - uncheck "Use cores grouping logic" - check "Combine CSS files by using media queries" Install AdvAgg Modifier if not enabled and go to `admin/config/development/performance/advagg/mod` + - Under "Move JS to the footer" Select "All" - set "Enable preprocess on all JS/CSS" - - set "Move JavaScript added by drupal_add_html_head() into drupal_add_js()" - - set "Move CSS added by drupal_add_html_head() into drupal_add_css()" + - set "Move JavaScript added by `drupal_add_html_head()` into `drupal_add_js()`" + - set "Move CSS added by `drupal_add_html_head()` into `drupal_add_css()`" - Enable every checkbox under "Optimize JavaScript/CSS Ordering" Install AdvAgg Compress Javascript if not enabled and go to `admin/config/development/performance/advagg/js-compress` + - Select JSMin if available; otherwise select JSMin+ **Other things to consider** @@ -413,6 +453,53 @@ using browser css/js conditionals (js browser conditionals backported from D8 https://drupal.org/node/865536) then the bundler might not meet your set value. +SETTINGS THAT DRUPAL.ORG USES +----------------------------- + +Issue: https://www.drupal.org/node/2493801 +These will not give you the best PageSpeed score but they will give the best +real world performance on slower connections. To expand on this, fourkitchens +has an excellent article on inlining critical css: +https://fourword.fourkitchens.com/article/use-grunt-and-advagg-inline-critical-css-drupal-7-theme + +Configuration: + + - Enable advanced aggregation: Checked + - Use DNS Prefetch for external CSS/JS: Enabled, below charset=utf-8 + - AdvAgg Cache Settings: Aggressive Render Cache ~ 10ms + - Combine CSS files by using media queries: Checked + - Fix improperly set type: Checked + - Cron Options: Default + - Obscure Options: Default + +Bundler: + + - Bundler is Active: Checked + - Target Number Of CSS Bundles Per Page: 2 + - Target Number Of JS Bundles Per Page: 5 + - Grouping logic: File size + +JS Compression: + + - File Compression: Select a Compressor: JSMin (~2ms) + - Inline Compression: Select a Compressor: JSMin (~2ms) + - Inline Compression: Use even if this page is not cacheable: Checked + +Modifications: + + - Remove ajaxPageState CSS and JS data if ajax.js is not used on this page: + Checked + - Move all external scripts to the top of the execution order: Checked + - Move all inline scripts to the bottom of the execution order: Checked + - Move Google Analytics analytics.js code from inline to be a file: Checked + - Prefetch stats.g.doubleclick.net/robots.txt: Checked + - Move JS to the footer: All but what is in the `$all_in_footer_list` + - Deferred JavaScript Execution: Add The defer Tag To All Script Tags: All but + external scripts + - Deferred inline JavaScript Execution: Put a wrapper around inline JS so it + runs from a setTimeout call: Checked + + NGINX CONFIGURATION ------------------- @@ -424,7 +511,6 @@ depending on your servers configuration. ### advagg_css and advagg_js support ### location ~* files/advagg_(?:css|js)/ { - access_log off; gzip_static on; access_log off; expires max; @@ -433,6 +519,9 @@ depending on your servers configuration. try_files $uri @drupal; } +Also noted that some ready made nginx configurations add in a Last-Modified +header inside the advagg directories. These should be removed. + TROUBLESHOOTING --------------- @@ -458,8 +547,8 @@ will redirect all anonymous requests to a login page. Most of the time there is a setting that allows certain pages to be excluded from the redirect. You should add the following to those exclusions. Note that sites/default/files is the location of you public file system (public://) so you might have to adjust this -to fit your setup. services/* is the default (CAS_EXCLUDE) and -httprl_async_function_callback is needed if httprl will be used. +to fit your setup. services/* is the default (`CAS_EXCLUDE`) and +`httprl_async_function_callback` is needed if httprl will be used. services/* sites/default/files/advagg_css/* @@ -471,21 +560,21 @@ page and under Redirection there should be a setting called "Excluded Pages". If Far-Future headers are not being sent out and you are using Apache here are -some tips to hopefully get it working. For Apache enable mod_rewrite, -mod_headers, and mod_expires. Add the following code to the bottom of Drupal's -core .htaccess file (located at the webroot level). +some tips to hopefully get it working. For Apache enable `mod_rewrite`, +`mod_headers`, and `mod_expires`. Add the following code to the bottom of +Drupal's core .htaccess file (located at the webroot level). - # No mod_headers + # No mod_headers. Apache module headers is not enabled. - # No mod_expires + # No mod_expires. Apache module expires is not enabled. # Use ETags. FileETag MTime Size - # Use Expires Directive. + # Use Expires Directive if apache module expires is enabled. # Do not use ETags. FileETag None @@ -495,6 +584,7 @@ core .htaccess file (located at the webroot level). ExpiresDefault A31449600 + # Use Headers Directive if apache module headers is enabled. # Do not use etags for cache validation. Header unset ETag @@ -515,7 +605,7 @@ core .htaccess file (located at the webroot level). If pages on the site stop working correctly or looks broken after Advanced CSS/JS Aggregation is enabled, the first step should be to validate the -individual CSS and/or JS files using the included advagg_validator module - +individual CSS and/or JS files using the included `advagg_validator` module - something as simple as an errant unfinished comment in one file may cause entire aggregates of files to be ignored. @@ -551,5 +641,13 @@ and Live instances. If you're getting the "HTTP requests to advagg are not getting though" error, -you can try to fix it by making sure the $base_url is correctly set for +you can try to fix it by making sure the `$base_url` is correctly set for production and not production environments. + + +If you're getting mixed content error for CSS JS files over HTTPS then you can +try to redirect all http traffic to be https. + + RewriteCond %{HTTPS} off + RewriteCond %{HTTP:X-Forwarded-Proto} !https + RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] diff --git a/sites/all/modules/contrib/advagg/advagg.admin.inc b/sites/all/modules/contrib/advagg/advagg.admin.inc index db5161978..ef94b812d 100644 --- a/sites/all/modules/contrib/advagg/advagg.admin.inc +++ b/sites/all/modules/contrib/advagg/advagg.admin.inc @@ -47,10 +47,15 @@ function advagg_admin_settings_form($form, $form_state) { '#description' => t('If HTTPRL is installed, advagg will use it to generate aggregates on the fly in a background parallel process.', array('@link' => 'http://drupal.org/project/httprl')), ); $form['global']['advagg_browser_dns_prefetch'] = array( - '#type' => 'checkbox', + '#type' => 'radios', '#title' => t('Use DNS Prefetch for external CSS/JS.'), '#default_value' => variable_get('advagg_browser_dns_prefetch', ADVAGG_BROWSER_DNS_PREFETCH), - '#description' => t('Start the DNS lookup for external CSS and JavaScript files as soon as possible.'), + '#description' => t('Start the DNS lookup for external CSS and JavaScript files as soon as possible. If you have css and/or js files being loaded right after the "charset=utf-8" meta tag, you need to use the "above charset=utf-8" option in order for dns-prefetch to work; Optimizely is an example that would need this other option.'), + '#options' => array( + 0 => t('Disabled'), + 1 => t('Enabled, below charset=utf-8 (recommended)'), + 2 => t('Enabled, above charset=utf-8'), + ), ); $aggressive_cache_conflicts = advagg_aggressive_cache_conflicts(); @@ -71,11 +76,12 @@ function advagg_admin_settings_form($form, $form_state) { '#title' => t('AdvAgg Cache Settings'), '#default_value' => variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL), '#options' => $options, - '#description' => t("As a reference, core takes about 25 ms to run. Development will scan all files for a change on every page load. Normal and the render cache is fine for all use cases. Aggressive should be fine for most use cases. If your inline css/js changes based off of a variable then the cache hit ratio will be low; if that is the case consider using Drupal.settings for a better cache hit ratio when using the render cache. The aggressive render cache will cache the output from js_alter and css_alter.", array( + '#description' => t("As a reference, core takes about 25 ms to run. Development will scan all files for a change on every page load. Normal and the render cache is fine for all use cases. Aggressive should be fine for most use cases. If your inline css/js changes based off of a variable then the cache hit ratio will be low; if that is the case consider using Drupal.settings for a better cache hit ratio when using the render cache. The aggressive render cache will cache the output from js_alter and css_alter. !description", array( '@information' => url($config_path . '/advagg/info', array( 'fragment' => 'edit-hooks-implemented', )), - )) . ' ' . $description, + '!description' => $description, + )), ); $form['global']['dev_container'] = array( @@ -182,6 +188,7 @@ function advagg_admin_settings_form($form, $form_state) { '#type' => 'select', '#options' => $long_times, '#title' => 'Delete aggregates accessed/modified more than a set time ago.', + // @codingStandardsIgnoreLine '#default_value' => variable_get('drupal_stale_file_threshold', 2592000), '#description' => t('The default value for this is %value.', array('%value' => format_interval(2592000))), ); @@ -259,6 +266,12 @@ function advagg_admin_settings_form($form, $form_state) { ), ), ); + $form['global']['obscure']['advagg_skip_file_create_url_inside_css'] = array( + '#type' => 'checkbox', + '#title' => t('Do not run CSS url() values through file_create_url().'), + '#default_value' => variable_get('advagg_skip_file_create_url_inside_css', ADVAGG_SKIP_FILE_CREATE_URL_INSIDE_CSS), + '#description' => t('If checked, the processing of url() values within CSS will be handled more like core than the CDN module. This has the advantage of not converting relative paths in your source CSS to absolute paths in the aggregated CSS.'), + ); $form['css'] = array( '#type' => 'fieldset', @@ -391,7 +404,7 @@ function advagg_admin_operations_form($form, $form_state) { $form['cron'] = array( '#type' => 'fieldset', '#title' => t('Cron Maintenance Tasks'), - '#description' => t('The following 4 operations are ran on cron but you can run them manually here.'), + '#description' => t('The following 6 operations are ran on cron but you can run them manually here.'), ); $form['cron']['smart_file_flush'] = array( '#type' => 'fieldset', @@ -405,6 +418,20 @@ function advagg_admin_operations_form($form, $form_state) { '#value' => t('Remove All Stale Files'), '#submit' => array('advagg_admin_flush_stale_files_button'), ); + + $form['cron']['delete_orphaned_aggregates'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Delete orphaned aggregates'), + '#description' => t('Scan CSS/JS advagg dir and remove file if there is no associated db record.'), + ); + $form['cron']['delete_orphaned_aggregates']['advagg_delete_orphaned_aggregates'] = array( + '#type' => 'submit', + '#value' => t('Delete orphaned aggregates'), + '#submit' => array('advagg_admin_delete_orphaned_aggregates_button'), + ); + $form['cron']['remove_missing_files'] = array( '#type' => 'fieldset', '#collapsible' => TRUE, @@ -417,6 +444,7 @@ function advagg_admin_operations_form($form, $form_state) { '#value' => t('Clear Missing Files From Database'), '#submit' => array('advagg_admin_remove_missing_files_from_db_button'), ); + $form['cron']['remove_old_aggregates'] = array( '#type' => 'fieldset', '#collapsible' => TRUE, @@ -429,6 +457,7 @@ function advagg_admin_operations_form($form, $form_state) { '#value' => t('Delete Unused Aggregates From Database'), '#submit' => array('advagg_admin_remove_old_unused_aggregates_button'), ); + $form['cron']['cleanup_semaphore_table'] = array( '#type' => 'fieldset', '#collapsible' => TRUE, @@ -438,10 +467,38 @@ function advagg_admin_operations_form($form, $form_state) { ); $form['cron']['cleanup_semaphore_table']['advagg_cleanup_semaphore_table'] = array( '#type' => 'submit', - '#value' => t('Delete Unused Aggregates From Database'), + '#value' => t('Delete Orphaned Semaphore Locks'), '#submit' => array('advagg_admin_cleanup_semaphore_table_button'), ); + $form['cron']['cleanup_temp_files'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Delete leftover temporary files'), + '#description' => t('Delete old temporary files from the filesystem.'), + ); + $form['cron']['cleanup_temp_files']['advagg_remove_temp_files'] = array( + '#type' => 'submit', + '#value' => t('Delete leftover temporary files'), + '#submit' => array('advagg_admin_cleanup_temp_files_button'), + ); + + if (module_exists('locale')) { + $form['cron']['refresh_locale_files'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Refresh all locale files'), + '#description' => t('Run all js files in the advagg_files table through locale_js_alter; loop for each enabled language.'), + ); + $form['cron']['refresh_locale_files']['advagg_refresh_all_locale_files'] = array( + '#type' => 'submit', + '#value' => t('Refresh all locale files'), + '#submit' => array('advagg_admin_refresh_locale_files_button'), + ); + } + // Hide drastic measures as they should not be done unless you really need it. $form['drastic_measures'] = array( '#type' => 'fieldset', @@ -685,7 +742,7 @@ function advagg_admin_info_form($form, $form_state) { $form['get_info_about_agg']['tip'] = array( '#markup' => '

' . t('Takes input like "@css_file" or a full aggregate name like "@advagg_js"', array( '@css_file' => $css_file, - '@advagg_js' => advagg_install_get_first_advagg_file($js_path[1]), + '@advagg_js' => advagg_install_get_first_advagg_file($js_path[1], 'js'), )) . '

', ); $form['get_info_about_agg']['wrapper'] = array( @@ -695,15 +752,37 @@ function advagg_admin_info_form($form, $form_state) { return $form; } +/** + * Add various advagg settings to the system_performance_settings form. + */ +function advagg_admin_system_performance_settings_form(&$form, $form_state) { + $msg = t('NOTE: If you wish to bypass aggregation for a set amount of time, you can go to the AdvAgg operations page and press the "aggregation bypass cookie" button.', array( + '@operations' => url('admin/config/development/performance/advagg/operations'), + )) . ' '; + if (user_access('bypass advanced aggregation')) { + $msg .= t('You can also selectively bypass aggregation by adding @code to the URL of any page.', array( + '@code' => '?advagg=0', + )); + } + else { + $msg .= t('You do not have the bypass advanced aggregation permission so adding @code to the URL will not work at this time for you; either grant this permission to your user role or use the bypass cookie if you wish to selectively bypass aggregation.', array( + '@permission' => url('admin/people/permissions', array('fragment' => 'module-advagg')), + '@code' => '?advagg=0', + )); + } + + $form['bandwidth_optimization']['advagg_note'] = array( + '#markup' => $msg, + ); +} + // Submit callback. /** * Clear out the advagg cache bin when the save configuration button is pressed. */ function advagg_admin_settings_form_submit($form, &$form_state) { - $cache_bins = advagg_flush_caches(); - foreach ($cache_bins as $bin) { - cache_clear_all('*', $bin, TRUE); - } + // Clear caches. + advagg_cache_clear_admin_submit(); } // Callbacks for buttons. @@ -733,6 +812,7 @@ function advagg_admin_toggle_bypass_cookie($form, &$form_state) { * Display file info in a drupal message. */ function advagg_admin_get_file_info_submit($form, &$form_state) { + // @codingStandardsIgnoreLine if (!empty($form_state['input']['ajax_page_state'])) { return; } @@ -868,6 +948,14 @@ function advagg_admin_get_file_info($filename) { * Perform a smart flush. */ function advagg_admin_flush_cache_button() { + // Clear the libraries cache. + if (function_exists('libraries_flush_caches')) { + $cache_tables = libraries_flush_caches(); + foreach ($cache_tables as $table) { + cache_clear_all('*', $table, TRUE); + } + } + // Run the command. module_load_include('inc', 'advagg', 'advagg.cache'); $flushed = advagg_push_new_changes(); @@ -907,6 +995,14 @@ function advagg_admin_flush_cache_button() { * Clear out all advagg cache bins. */ function advagg_admin_clear_all_caches_button() { + // Clear the libraries cache. + if (function_exists('libraries_flush_caches')) { + $cache_tables = libraries_flush_caches(); + foreach ($cache_tables as $table) { + cache_clear_all('*', $table, TRUE); + } + } + // Run the command. module_load_include('inc', 'advagg', 'advagg.cache'); advagg_flush_all_cache_bins(); @@ -969,6 +1065,22 @@ function advagg_admin_increment_global_counter() { drupal_set_message(t('Global counter is now set to %new_value', array('%new_value' => $new_value))); } +/** + * Scan CSS/JS advagg dir and remove file if there is no associated db record. + */ +function advagg_admin_delete_orphaned_aggregates_button() { + module_load_include('inc', 'advagg', 'advagg.cache'); + + // Remove aggregates that include missing files. + $deleted = advagg_delete_orphaned_aggregates(); + if (empty($deleted[0]) && empty($deleted[1])) { + drupal_set_message(t('All files have an associated db record; nothing was deleted.')); + } + else { + drupal_set_message(t('Some files had no associated db record and could be safely deleted from the file system. @raw', array('@raw' => print_r($deleted[1], TRUE)))); + } +} + /** * Scan for missing files and remove the associated entries in the database. */ @@ -1013,6 +1125,38 @@ function advagg_admin_cleanup_semaphore_table_button() { drupal_set_message(t('No orphaned advagg semaphore database table locks discovered. Nothing was deleted.')); } else { - drupal_set_message(t('Some orphaned advagg semaphore database table locks discovered were found. A total of %count database entries were removed.', array('%count' => $count))); + drupal_set_message(format_plural($count, '1 orphaned advagg semaphore database table lock was found. A total of 1 database entry was removed.', 'Some orphaned advagg semaphore database table locks were found. A total of @count database entries were removed.')); + } +} + +/** + * Delete orphaned/expired advagg locks from the semaphore database table. + */ +function advagg_admin_refresh_locale_files_button() { + module_load_include('inc', 'advagg', 'advagg.cache'); + + // Refresh all locale files. + $locale_files = advagg_refresh_all_locale_files(); + if (empty($locale_files)) { + drupal_set_message(t('No locale files are being used.')); + } + else { + drupal_set_message(t('The following locale files are being used:
@files
', array('@files' => print_r($locale_files, TRUE)))); + } +} + +/** + * Delete leftover temp files. + */ +function advagg_admin_cleanup_temp_files_button() { + module_load_include('inc', 'advagg', 'advagg.cache'); + + // Delete orphaned/expired advagg locks from the semaphore database table. + $count = advagg_remove_temp_files(); + if (empty($count)) { + drupal_set_message(t('No leftover temp files found. Nothing was deleted.')); + } + else { + drupal_set_message(format_plural($count, '1 leftover temp files from advagg was found. A total of 1 temp files was removed.', 'Some leftover temp files from advagg were found. A total of %count temp files were removed.')); } } diff --git a/sites/all/modules/contrib/advagg/advagg.admin.js b/sites/all/modules/contrib/advagg/advagg.admin.js index 999a75fe1..c89578fbd 100644 --- a/sites/all/modules/contrib/advagg/advagg.admin.js +++ b/sites/all/modules/contrib/advagg/advagg.admin.js @@ -8,18 +8,18 @@ /** * Test to see if the given string contains unicode. * - * @param int interval + * @param {int} interval * String to test. - * @param int granularity + * @param {int} granularity * String to test. - * @param string langcode + * @param {string} langcode * Language used in translation. * - * @return + * @return {bool} * true if string contains non ASCII characters. * false if string only contains ASCII characters. */ -Drupal.formatInterval = function(interval, granularity, langcode) { +Drupal.formatInterval = function (interval, granularity, langcode) { "use strict"; granularity = typeof granularity !== 'undefined' ? granularity : 2; langcode = typeof langcode !== 'undefined' ? langcode : null; @@ -66,14 +66,14 @@ Drupal.formatInterval = function(interval, granularity, langcode) { /** * Test to see if the given string contains unicode. * - * @param str + * @param {string} str * String to test. * - * @return + * @return {bool} * true if string contains non ASCII characters. * false if string only contains ASCII characters. */ -function advagg_is_unicode(str){ +function advagg_is_unicode(str) { "use strict"; for (var i = 0, n = str.length; i < n; i++) { if (str.charCodeAt(i) > 255) { @@ -86,7 +86,7 @@ function advagg_is_unicode(str){ /** * Toggle the advagg cookie. * - * @return + * @return {bool} * true if hostname contains unicode. * false so the form does not get submitted. */ diff --git a/sites/all/modules/contrib/advagg/advagg.advagg.inc b/sites/all/modules/contrib/advagg/advagg.advagg.inc index ec92934db..3b4c0dd77 100644 --- a/sites/all/modules/contrib/advagg/advagg.advagg.inc +++ b/sites/all/modules/contrib/advagg/advagg.advagg.inc @@ -30,7 +30,12 @@ function advagg_advagg_save_aggregate_alter(array &$files_to_save, array $aggreg $gzip_exists = FALSE; foreach ($files_to_save as $uri => $contents) { // See if this uri contains .gz near the end of it. - $pos = strripos($uri, '.gz', 91 + strlen(ADVAGG_SPACE) * 3); + if (strlen($uri) > 91 + strlen(ADVAGG_SPACE) * 3) { + $pos = strripos($uri, '.gz', 91 + strlen(ADVAGG_SPACE) * 3); + } + else { + $pos = strripos($uri, '.gz'); + } if (!empty($pos)) { $len = strlen($uri); // .gz file exists, exit loop. @@ -219,7 +224,7 @@ function advagg_advagg_get_info_on_files_alter(&$return, $cached_data, $bypass_c // Get the css path. list($css_path) = advagg_get_root_files_dir(); - $parts_path = $css_path[1] . '/parts'; + $parts_path = $css_path[1] . '/parts/'; foreach ($return as &$info) { // Skip if not a css file. @@ -228,7 +233,7 @@ function advagg_advagg_get_info_on_files_alter(&$return, $cached_data, $bypass_c } // Check if this is a split css file. - if (strpos($info['data'], $parts_path) === 0) { + if (strpos($info['data'], $parts_path) !== FALSE) { $info['split'] = TRUE; } // Break large file into multiple small files if needed. @@ -246,7 +251,7 @@ function advagg_advagg_get_info_on_files_alter(&$return, $cached_data, $bypass_c continue; } // Get the file contents. - $file_contents = file_get_contents($info['data']); + $file_contents = (string) @file_get_contents($info['data']); // Get domain names in this css file. $matches = array(); @@ -322,7 +327,7 @@ function advagg_advagg_changed_files(array $files, array $types) { // Skip if the file that was changed is not in this themes directory. $theme_path = drupal_get_path('theme', $theme_name); - if (strpos($css_file, $theme_path) !== 0) { + if ((!empty($theme_path)) && strpos($css_file, $theme_path) !== 0) { continue; } $files_in_theme[] = $css_file; @@ -417,6 +422,49 @@ function advagg_advagg_changed_files(array $files, array $types) { } } +/** + * Implements hook_advagg_changed_files(). + * + * If the locale module is enabled regenerate locale translations. + * + * @param array $files + * List of files that have changed. + * @param array $types + * Array with the css and or the js key. + */ +function locale_advagg_changed_files(array $files, array $types) { + // Skip if no js changed. + if (empty($types['js'])) { + return; + } + + $javascript = array(); + foreach ($files as $filename => $meta_data) { + // Only care about js files. + $ext = pathinfo($filename, PATHINFO_EXTENSION); + if ($ext != 'js') { + continue; + } + $javascript[] = array( + 'type' => 'file', + 'data' => $filename, + ); + } + if (!empty($javascript)) { + $javascript_before = $javascript; + $language_before = $GLOBALS['language']; + $language_list = language_list(); + foreach ($language_list as $lang) { + if ($lang->enabled) { + $GLOBALS['language'] = $lang; + $javascript = $javascript_before; + locale_js_alter($javascript); + } + } + $GLOBALS['language'] = $language_before; + } +} + /** * Implements hook_advagg_scan_for_changes(). * diff --git a/sites/all/modules/contrib/advagg/advagg.cache.inc b/sites/all/modules/contrib/advagg/advagg.cache.inc index 651f1abe4..3ec3dbaf3 100644 --- a/sites/all/modules/contrib/advagg/advagg.cache.inc +++ b/sites/all/modules/contrib/advagg/advagg.cache.inc @@ -292,6 +292,10 @@ function advagg_remove_all_aggregated_files($kill_htaccess = FALSE) { 'nomask' => '/(\.\.?|CVS)$/', ); list($css_files, $js_files) = advagg_get_all_files($options); + // Let other modules know about the removed files. + // Call hook_advagg_removed_aggregates(). + module_invoke_all('advagg_removed_aggregates', $css_files); + module_invoke_all('advagg_removed_aggregates', $js_files); // Remove the htaccess files as well. if ($kill_htaccess) { @@ -450,7 +454,7 @@ function advagg_delete_orphaned_aggregates() { function advagg_delete_files_if_orphaned(array $files) { // Get the uri for the advagg_css/parts directory. list($css_path) = advagg_get_root_files_dir(); - $parts_uri = $css_path[0] . '/parts'; + $parts_uri = $css_path[0] . '/parts/'; // Array used to record what files were deleted. $kill_list = $keyed_file_list = array(); @@ -469,7 +473,7 @@ function advagg_delete_files_if_orphaned(array $files) { $start = strpos($file->uri, $parts_uri); if ($start !== FALSE) { // Get the original filename. - $original_file = substr($file->uri, $start + strlen($parts_uri) + 1); + $original_file = substr($file->uri, $start + strlen($parts_uri)); $original_file = preg_replace('/(.\\d+\\.css)$/i', '.css', $original_file); if (file_exists($original_file)) { // Original file exists, do not delete. @@ -513,6 +517,10 @@ function advagg_delete_files_if_orphaned(array $files) { } } } + + // Let other modules know about the removed files. + // Call hook_advagg_removed_aggregates(). + module_invoke_all('advagg_removed_aggregates', $kill_list); return $kill_list; } @@ -590,6 +598,191 @@ function advagg_cleanup_semaphore_table() { return $results; } +/** + * Delete leftover temp files. + * + * @return int + * Count of the number of files removed + */ +function advagg_remove_temp_files() { + // Make sure advagg_get_root_files_dir() is available. + drupal_load('module', 'advagg'); + // Make sure advagg_install_delete_empty_file_if_stale() is available. + module_load_include('install', 'advagg', 'advagg'); + + // Get the advagg paths. + $advagg_path = advagg_get_root_files_dir(); + $total_count = 0; + // Get the top level path. + $top_level = substr($advagg_path[0][0], 0, strpos($advagg_path[0][0], 'advagg_css')); + + // Remove empty temp files from public://. + $files = file_scan_directory($top_level, '/file.*|fil.*\.tmp/', array( + 'recurse' => FALSE, + 'callback' => 'advagg_install_delete_empty_file_if_stale', + )); + foreach ($files as $key => $file) { + if (file_exists($file->uri)) { + unset($files[$key]); + } + } + $total_count += count($files); + + // Remove empty temp files from public://advagg_css. + $files = file_scan_directory($advagg_path[0][0], '/file.*|fil.*\.tmp/', array( + 'recurse' => FALSE, + 'callback' => 'advagg_install_delete_empty_file_if_stale', + )); + foreach ($files as $key => $file) { + if (file_exists($file->uri)) { + unset($files[$key]); + } + } + $total_count += count($files); + + // Remove empty temp files from public://advagg_js. + $files = file_scan_directory($advagg_path[1][0], '/file.*|fil.*\.tmp/', array( + 'recurse' => FALSE, + 'callback' => 'advagg_install_delete_empty_file_if_stale', + )); + foreach ($files as $key => $file) { + if (file_exists($file->uri)) { + unset($files[$key]); + } + } + $total_count += count($files); + + // Remove empty temp files from public://. + $files = file_scan_directory($top_level, '/file_advagg_.*/', array( + 'recurse' => FALSE, + 'callback' => 'advagg_delete_temp_file_if_stale', + )); + foreach ($files as $key => $file) { + if (file_exists($file->uri)) { + unset($files[$key]); + } + } + $total_count += count($files); + + // Remove empty temp files from public://advagg_css. + $files = file_scan_directory($advagg_path[0][0], '/file_advagg_.*/', array( + 'recurse' => FALSE, + 'callback' => 'advagg_delete_temp_file_if_stale', + )); + foreach ($files as $key => $file) { + if (file_exists($file->uri)) { + unset($files[$key]); + } + } + $total_count += count($files); + + // Remove empty temp files from public://advagg_js. + $files = file_scan_directory($advagg_path[1][0], '/file_advagg_.*/', array( + 'recurse' => FALSE, + 'callback' => 'advagg_delete_temp_file_if_stale', + )); + foreach ($files as $key => $file) { + if (file_exists($file->uri)) { + unset($files[$key]); + } + } + $total_count += count($files); + + // Remove empty temp files from public://. + $files = file_scan_directory($top_level, '/advagg_file_.*/', array( + 'recurse' => FALSE, + 'callback' => 'advagg_delete_temp_file_if_stale', + )); + foreach ($files as $key => $file) { + if (file_exists($file->uri)) { + unset($files[$key]); + } + } + $total_count += count($files); + + // Remove empty temp files from public://advagg_css. + $files = file_scan_directory($advagg_path[0][0], '/advagg_file_.*/', array( + 'recurse' => FALSE, + 'callback' => 'advagg_delete_temp_file_if_stale', + )); + foreach ($files as $key => $file) { + if (file_exists($file->uri)) { + unset($files[$key]); + } + } + $total_count += count($files); + + // Remove empty temp files from public://advagg_js. + $files = file_scan_directory($advagg_path[1][0], '/advagg_file_.*/', array( + 'recurse' => FALSE, + 'callback' => 'advagg_delete_temp_file_if_stale', + )); + foreach ($files as $key => $file) { + if (file_exists($file->uri)) { + unset($files[$key]); + } + } + $total_count += count($files); + + // Output info. + return $total_count; +} + +/** + * Refresh all locale files. + * + * @return int + * Count of the number of files removed + */ +function advagg_refresh_all_locale_files() { + $locale_files = array(); + if (!module_exists('locale')) { + return $locale_files; + } + + $results = db_select('advagg_files', 'af') + ->fields('af') + ->condition('af.filetype', 'js') + ->execute(); + $javascript = array(); + foreach ($results as $row) { + $javascript[] = array( + 'type' => 'file', + 'data' => $row->filename, + ); + } + + if (!empty($javascript)) { + $javascript_before = $javascript; + $language_before = $GLOBALS['language']; + $language_list = language_list(); + foreach ($language_list as $lang) { + if ($lang->enabled) { + $GLOBALS['language'] = $lang; + $javascript = $javascript_before; + locale_js_alter($javascript); + $locale_file = array_diff_key($javascript, $javascript_before); + $locale_files += $locale_file; + } + } + $GLOBALS['language'] = $language_before; + } + return $locale_files; +} + +/** + * Callback to delete files if modified more than 60 seconds ago. + * + * @param string $uri + * Location of the file to check. + */ +function advagg_delete_temp_file_if_stale($uri) { + // Set stale file threshold to 60 seconds. + if (REQUEST_TIME - filemtime($uri) > 60) { + file_unmanaged_delete($uri); + } +} + /** * See if any of the subfiles has changed. * diff --git a/sites/all/modules/contrib/advagg/advagg.drush.inc b/sites/all/modules/contrib/advagg/advagg.drush.inc index 4e9f8ff0b..3028baf95 100644 --- a/sites/all/modules/contrib/advagg/advagg.drush.inc +++ b/sites/all/modules/contrib/advagg/advagg.drush.inc @@ -146,20 +146,56 @@ function drush_advagg_cron() { drush_log(dt('No stale aggregates found. Nothing was deleted.'), 'ok'); } + // Output results from running advagg_delete_orphaned_aggregates(). + if (empty($output[1][0]) && empty($output[1][1])) { + drush_log(dt('All files have an associated db record; nothing was deleted.'), 'ok'); + } + else { + drush_log(dt('Some files had no associated db record and could be safely deleted from the file system. @raw', array('@raw' => print_r($output[1], TRUE))), 'ok'); + } + // Output results from running advagg_remove_missing_files_from_db(). - if (empty($output[1])) { - drush_log(dt('No missing files found and/or could be safely cleared out of the database.'), 'ok'); + if (empty($output[2])) { + drupal_set_message(dt('All source files where found, no database entries where pruned.'), 'ok'); } else { - drush_log(dt('Some missing files were found and could be safely cleared out of the database. @raw', array('@raw' => print_r($output[1], TRUE))), 'ok'); + // format_plural() not always available. + drupal_set_message(dt('Some source files are missing and as a result some unused aggregates were found. A total of %count database entries were removed.', array('%count' => count($output[2]))), 'ok'); } // Output results from running advagg_remove_old_unused_aggregates(). - if (empty($output[2])) { - drupal_set_message(t('No old and unused aggregates found. Nothing was deleted.'), 'ok'); + if (empty($output[3])) { + drupal_set_message(dt('No old and unused aggregates found. Nothing was deleted.'), 'ok'); } else { - drupal_set_message(t('Some old and unused aggregates were found. A total of %count database entries were removed.', array('%count' => $output[2])), 'ok'); + // format_plural() not always available. + drupal_set_message(dt('Some old and unused aggregates were found. A total of %count database entries were removed.', array('%count' => $output[3])), 'ok'); + } + + // Output results from running advagg_cleanup_semaphore_table(). + if (empty($output[4])) { + drupal_set_message(dt('No old semaphore locks found.'), 'ok'); + } + else { + // format_plural() not always available. + drupal_set_message(dt('A total of %count old semaphore entries were removed.', array('%count' => count($output[4]))), 'ok'); + } + + // Output results from running advagg_remove_temp_files(). + if (empty($output[5])) { + drupal_set_message(dt('No leftover temporary files found. Nothing was deleted.'), 'ok'); + } + else { + // format_plural() not always available. + drupal_set_message(dt('Some oleftover temporary files were found. A total of %count temporary files were removed.', array('%count' => $output[5])), 'ok'); + } + + // Output results from running advagg_refresh_all_locale_files(). + if (empty($output[6])) { + drupal_set_message(dt('Locale did not translate anything in any JavaScript files.'), 'ok'); + } + else { + drupal_set_message(dt('Locale did translate some JavaScript files. Resulting locale js files: @files', array('@files' => print_r($output[6], TRUE))), 'ok'); } } @@ -167,6 +203,14 @@ function drush_advagg_cron() { * Flush the correct caches so CSS/JS changes go live. */ function drush_advagg_smart_cache_flush() { + // Clear the libraries cache. + if (function_exists('libraries_flush_caches')) { + $cache_tables = libraries_flush_caches(); + foreach ($cache_tables as $table) { + cache_clear_all('*', $table, TRUE); + } + } + // Run the command. module_load_include('inc', 'advagg', 'advagg.cache'); $flushed = advagg_push_new_changes(); diff --git a/sites/all/modules/contrib/advagg/advagg.inc b/sites/all/modules/contrib/advagg/advagg.inc index 95b1b305a..9252e047e 100644 --- a/sites/all/modules/contrib/advagg/advagg.inc +++ b/sites/all/modules/contrib/advagg/advagg.inc @@ -75,7 +75,7 @@ function advagg_insert_aggregate_version($aggregate_filenames_hash, $aggregate_c ->key(array( 'aggregate_filenames_hash' => $record['aggregate_filenames_hash'], 'aggregate_contents_hash' => $record['aggregate_contents_hash'], - )) + )) ->insertFields($record) ->execute(); return $return; @@ -133,7 +133,7 @@ function advagg_insert_aggregate(array $files, $aggregate_filenames_hash) { ->key(array( 'aggregate_filenames_hash' => $record['aggregate_filenames_hash'], 'filename_hash' => $record['filename_hash'], - )) + )) ->insertFields($record) ->execute(); @@ -175,6 +175,8 @@ function advagg_insert_update_files(array $files, $type) { } } + // Make drupal_get_installed_schema_version() available. + include_once DRUPAL_ROOT . '/includes/install.inc'; foreach ($files as $filename => $file_meta_data) { // Create record. $record = array( @@ -190,13 +192,25 @@ function advagg_insert_update_files(array $files, $type) { // Check the file in the database. if (empty($files_in_db[$filename])) { try { + // Add in filesize_processed if the schema is 7210 or higher. + if (drupal_get_installed_schema_version('advagg') >= 7210) { + $record['filesize_processed'] = (int) advagg_generate_filesize_processed($filename, $type); + } + // Add in use_strict if the schema is 7212 or higher. + if (drupal_get_installed_schema_version('advagg') >= 7212) { + $record['use_strict'] = 0; + if ($type === 'js') { + $record['use_strict'] = (int) advagg_does_js_start_with_use_strict($filename); + } + } + // Insert into database. $record['changes'] = 1; $return = db_merge('advagg_files') ->key(array( 'filename_hash' => $record['filename_hash'], - )) + )) ->insertFields($record) ->execute(); if ($return) { @@ -206,6 +220,10 @@ function advagg_insert_update_files(array $files, $type) { catch (PDOException $e) { // If it fails we don't care, the file was added to the table by another // process then. + // Still log it if in development mode. + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) { + watchdog('advagg', 'Development Mode - Caught PDO Exception: @info', array('@info' => $e)); + } } } else { @@ -219,10 +237,24 @@ function advagg_insert_update_files(array $files, $type) { $diff['changes'] = $changes + 1; $diff['filename_hash'] = $record['filename_hash']; + // Add in filesize_processed if the schema is 7210 or higher. + if (drupal_get_installed_schema_version('advagg') >= 7210) { + $diff['filesize_processed'] = (int) advagg_generate_filesize_processed($filename, $type); + } + if (drupal_get_installed_schema_version('advagg') >= 7212) { + $diff['use_strict'] = 0; + if ($type === 'js') { + $diff['use_strict'] = (int) advagg_does_js_start_with_use_strict($filename); + if (empty($diff['use_strict'])) { + $diff['use_strict'] = 0; + } + } + } + $return = db_merge('advagg_files') ->key(array( 'filename_hash' => $diff['filename_hash'], - )) + )) ->fields($diff) ->execute(); if ($return) { @@ -234,6 +266,104 @@ function advagg_insert_update_files(array $files, $type) { return $write_done; } +/** + * Given a filename calculate the processed filesize. + * + * @param string $filename + * String; filename containing path information as well. + * @param string $type + * String; css or js. + * + * @return int + * Processed filesize. + */ +function advagg_generate_filesize_processed($filename, $type) { + $files = &drupal_static(__FUNCTION__, array()); + if (!isset($files[$type][$filename])) { + // Make advagg_get_*_aggregate_contents() available. + module_load_include('inc', 'advagg', 'advagg.missing'); + $aggregate_settings = advagg_current_hooks_hash_array(); + + $file_aggregate = array($filename => array()); + if ($type === 'css') { + list($contents) = advagg_get_css_aggregate_contents($file_aggregate, $aggregate_settings); + } + elseif ($type === 'js') { + list($contents) = advagg_get_js_aggregate_contents($file_aggregate, $aggregate_settings); + } + if (!empty($contents)) { + $compressed = gzencode($contents, 9, FORCE_GZIP); + $files[$type][$filename] = strlen($compressed); + } + else { + $files[$type][$filename] = 0; + } + } + return $files[$type][$filename]; +} + +/** + * Given a js string, see if "use strict"; is the first thing ran. + * + * @param string $filename + * String; filename containing path information as well. + * + * @return bool + * True if "use strict"; is the first thing ran. + */ +function advagg_does_js_start_with_use_strict($filename) { + $files = &drupal_static(__FUNCTION__, array()); + if (!isset($files[$filename])) { + // Make advagg_get_*_aggregate_contents() available. + module_load_include('inc', 'advagg', 'advagg.missing'); + $aggregate_settings = advagg_current_hooks_hash_array(); + + $file_aggregate = array($filename => array()); + list($contents) = advagg_get_js_aggregate_contents($file_aggregate, $aggregate_settings); + + // See if the js file starts with "use strict";. + $use_strict = stripos(trim($contents), '"use strict";'); + $strict_js = FALSE; + if ($use_strict !== FALSE) { + if ($use_strict == 0) { + $strict_js = TRUE; + } + else { + // Get all text before "use strict";. + $substr = substr($contents, 0, $use_strict); + // Check if there are any comments. + $single_line_comment = stripos($substr, '//'); + $multi_line_comment = stripos($substr, '/*'); + $in_function = stripos($substr, '{'); + if ($single_line_comment !== FALSE || $multi_line_comment !== FALSE) { + // Include jsminplus.inc and advagg_js_compress.advagg.inc. + if (!function_exists('advagg_js_compress_jsminplus')) { + include_once drupal_get_path('module', 'advagg') . '/advagg_js_compress/advagg_js_compress.advagg.inc'; + if (!class_exists('JSMinPlus')) { + include drupal_get_path('module', 'advagg') . '/advagg_js_compress/jsminplus.inc'; + $nesting_level = ini_get('xdebug.max_nesting_level'); + if (!empty($nesting_level) && $nesting_level < 200) { + ini_set('xdebug.max_nesting_level', 200); + } + } + } + // Jsminplus strips all comments. + advagg_js_compress_jsminplus($contents, FALSE); + $use_strict = stripos($contents, '"use strict"'); + $substr = substr($contents, 0, $use_strict); + // Check if there is a function before this. + $in_function = stripos($substr, '{'); + } + if ($in_function === FALSE) { + $strict_js = TRUE; + } + } + } + $files[$filename] = $strict_js; + } + return $files[$filename]; +} + // File operations. /** * Given a group of files calculate what the aggregate filename will be. @@ -382,8 +512,8 @@ function &advagg_load_files_info_into_static_cache(array $files) { */ function advagg_drupal_hash_base64($file) { // Get the static cache of this data. - $cached_data = &drupal_static('advagg_drupal_hash_base64'); - if (!isset($cached_data[$file])) { + $cached_data = &drupal_static('advagg_drupal_hash_base64', array()); + if (!array_key_exists($file, $cached_data)) { $cached_data[$file] = drupal_hash_base64($file); } return $cached_data[$file]; @@ -433,7 +563,7 @@ function advagg_get_info_on_files(array $files, $bypass_cache = FALSE, $run_alte advagg_clearstatcache($file); // Remove file in the cache if it does not exist. - if (!file_exists($file)) { + if (!file_exists($file) || is_dir($file)) { if (isset($cached_data[$cache_id])) { cache_clear_all($cache_id, 'cache_advagg_info', FALSE); } @@ -452,11 +582,11 @@ function advagg_get_info_on_files(array $files, $bypass_cache = FALSE, $run_alte } // Get the file contents. - $file_contents = file_get_contents($file); + $file_contents = (string) @file_get_contents($file); $ext = pathinfo($file, PATHINFO_EXTENSION); if ($ext !== 'css' && $ext !== 'js') { - // Get the $ext from the database, + // Get the $ext from the database. $row = db_select('advagg_files', 'af') ->fields('af') ->condition('filename', $file) @@ -481,7 +611,7 @@ function advagg_get_info_on_files(array $files, $bypass_cache = FALSE, $run_alte // Build meta data array and set cache. $return[$file] = array( - 'filesize' => filesize($file), + 'filesize' => (int) @filesize($file), 'mtime' => @filemtime($file), 'filename_hash' => $filename_hash, 'content_hash' => drupal_hash_base64($file_contents), @@ -604,11 +734,13 @@ function advagg_clearstatcache($filename = NULL) { * * @param array $files_to_aggregate * An array of CSS/JS groups. + * @param string $type + * String; css or js. * * @return array * New version of groups. */ -function advagg_generate_groups(array $files_to_aggregate) { +function advagg_generate_groups(array $files_to_aggregate, $type) { $groups = array(); $count = 0; $location = 0; @@ -618,6 +750,7 @@ function advagg_generate_groups(array $files_to_aggregate) { $async = ''; $cache = ''; $scope = ''; + $use_strict = 0; $browsers = array(); $selector_count = 0; // Get CSS limit value. @@ -638,6 +771,21 @@ function advagg_generate_groups(array $files_to_aggregate) { $files_info = advagg_get_info_on_files($filenames, TRUE); } + $strict_files = array(); + if ($type == 'js') { + // Make drupal_get_installed_schema_version() available. + include_once DRUPAL_ROOT . '/includes/install.inc'; + if (drupal_get_installed_schema_version('advagg') >= 7213) { + $query = db_select('advagg_files', 'af') + ->fields('af', array('filename', 'use_strict')) + ->condition('use_strict', 1) + ->execute(); + foreach ($query as $row) { + $strict_files[$row->filename] = $row->use_strict; + } + } + } + foreach ($files_to_aggregate as $data) { foreach ($data as $values) { @@ -651,7 +799,7 @@ function advagg_generate_groups(array $files_to_aggregate) { $ext = isset($file_info['fileext']) ? $file_info['fileext'] : pathinfo($file_info['data'], PATHINFO_EXTENSION); if ($ext !== 'css' && $ext !== 'js') { if (empty($last_ext)) { - // Get the $ext from the database, + // Get the $ext from the database. $row = db_select('advagg_files', 'af') ->fields('af') ->condition('filename', $file_info['data']) @@ -695,6 +843,16 @@ function advagg_generate_groups(array $files_to_aggregate) { $browsers = array(); } + if (!empty($strict_files[$file_info['data']]) && $use_strict != $strict_files[$file_info['data']]) { + // use_strict value changed to 1. + $changed = TRUE; + $use_strict = 1; + } + if (!empty($use_strict) && empty($strict_files[$file_info['data']])) { + // use_strict value changed to 0. + $changed = TRUE; + $use_strict = 0; + } if (isset($file_info['defer']) && $defer != $file_info['defer']) { // Defer value changed. $changed = TRUE; @@ -863,7 +1021,13 @@ function advagg_split_css_file(array $file_info) { if ($last_chunk) { $file_info['split_last_part'] = TRUE; } - $parts[] = advagg_create_subfile($chunks, $overall_split, $file_info); + $subfile = advagg_create_subfile($chunks, $overall_split, $file_info); + if (empty($subfile)) { + // Somthing broke; do not create a subfile. + watchdog('advagg', 'Spliting up a CSS file failed. File info: @info', array('@info' => var_export($file_info, TRUE))); + return array(); + } + $parts[] = $subfile; continue; } @@ -978,7 +1142,13 @@ function advagg_split_css_file(array $file_info) { $file_info['split_last_part'] = TRUE; } // Write the data. - $parts[] = advagg_create_subfile($string_to_write, $overall_split, $file_info); + $subfile = advagg_create_subfile($string_to_write, $overall_split, $file_info); + if (empty($subfile)) { + // Somthing broke; do not create a subfile. + watchdog('advagg', 'Spliting up a CSS file failed. File info: @info', array('@info' => var_export($file_info, TRUE))); + return array(); + } + $parts[] = $subfile; $first = FALSE; } } @@ -996,7 +1166,7 @@ function advagg_split_css_file(array $file_info) { * File info array from advagg_get_info_on_file(). * * @return array - * Array with advagg_get_info_on_file data and split data. + * Array with advagg_get_info_on_file data and split data; FALSE on failure. */ function advagg_create_subfile($css, $overall_split, array $file_info) { static $parts_uri; @@ -1015,6 +1185,9 @@ function advagg_create_subfile($css, $overall_split, array $file_info) { // Get the path from $file_info['data']. $uri_path = advagg_get_relative_path($file_info['data']); + if (!file_exists($uri_path) || is_dir($uri_path)) { + return FALSE; + } // Write the current chunk of the CSS into a file. $new_filename = str_ireplace('.css', '.' . $overall_split . '.css', $uri_path); @@ -1073,7 +1246,7 @@ function advagg_build_aggregate_plans(array $files_to_aggregate, $type) { } // Place into biggest grouping possible. - $groups = advagg_generate_groups($files_to_aggregate); + $groups = advagg_generate_groups($files_to_aggregate, $type); // Get filenames. $files = advagg_generate_filenames($groups, $type); @@ -1167,6 +1340,10 @@ function advagg_build_aggregate_plans(array $files_to_aggregate, $type) { advagg_create_aggregate_files($plans, $type); } + // Run hooks to modify the plans. + // Call hook_advagg_build_aggregate_plans_post_alter(). + drupal_alter('advagg_build_aggregate_plans_post', $plans); + return $plans; } @@ -1252,24 +1429,29 @@ function advagg_create_aggregate_files(array $plans, $type) { * @return string * Contents of the stylesheet, including any resolved @import commands. */ -function advagg_load_css_stylesheet($file, $optimize, array $aggregate_settings = array()) { +function advagg_load_css_stylesheet($file, $optimize = TRUE, array $aggregate_settings = array(), $contents = '') { $old_base_path = $GLOBALS['base_path']; // Change context to that of when this aggregate was created. advagg_context_switch($aggregate_settings, 0); // Get the stylesheets contents. - $contents = advagg_load_stylesheet($file, $optimize); + $contents = advagg_load_stylesheet($file, $optimize, TRUE, $contents); // Get the parent directory of this file, relative to the Drupal root. $css_base_url = substr($file, 0, strrpos($file, '/')); - $url_parts = explode('advagg_css/parts/', $css_base_url); + + // Handle split css files. + list($css_path) = advagg_get_root_files_dir(); + $parts_path = $css_path[1] . '/parts/'; + $url_parts = strpos($css_base_url, $parts_path); // If this CSS file is actually a part of a previously split larger CSS file, // don't use it to construct relative paths within the CSS file for // 'url( ... )' bits. - if (count($url_parts) === 2) { - $css_base_url = $url_parts[1]; + if ($url_parts !== FALSE) { + $css_base_url = substr($css_base_url, $url_parts + strlen($parts_path)); } + // Replace the old base path with the one that was passed in. $pos = strpos($css_base_url, $old_base_path); if ($pos !== FALSE) { @@ -1277,10 +1459,9 @@ function advagg_load_css_stylesheet($file, $optimize, array $aggregate_settings } _advagg_build_css_path(array(), $css_base_url . '/', $aggregate_settings); - // Anchor all paths in the CSS with its base URL, ignoring external, // absolute paths, and urls that start with # or %23 (SVG). - $contents = preg_replace_callback('%url\(\s*+[\'"]?+(?![a-z]++:|/|\#|\%23+)([^\'"()\s]++)[\'"]?+\s*+\)%i', '_advagg_build_css_path', $contents); + $contents = preg_replace_callback('%url\(\s*+[\'"]?+(?![a-z]++:|/|\#|\%23+)([^\'"\)]++)[\'"]?+\s*+\)%i', '_advagg_build_css_path', $contents); // Change context back. advagg_context_switch($aggregate_settings, 1); @@ -1365,6 +1546,19 @@ function _advagg_build_css_path(array $matches, $base = '', array $aggregate_set } $fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : ''; - $url = advagg_file_create_url($base_url, $_aggregate_settings); + if (variable_get('advagg_skip_file_create_url_inside_css', ADVAGG_SKIP_FILE_CREATE_URL_INSIDE_CSS)) { + if (isset($_aggregate_settings['variables']['base_path'])) { + $url = $_aggregate_settings['variables']['base_path'] . $url; + } + else { + $url = $GLOBALS['base_path'] . $url; + } + $query = ''; + $fragment = ''; + } + else { + $url = advagg_file_create_url($base_url, $_aggregate_settings); + } + return 'url(' . $url . $query . $fragment . ')'; } diff --git a/sites/all/modules/contrib/advagg/advagg.info b/sites/all/modules/contrib/advagg/advagg.info index 480fdd222..e2bb26983 100644 --- a/sites/all/modules/contrib/advagg/advagg.info +++ b/sites/all/modules/contrib/advagg/advagg.info @@ -6,9 +6,9 @@ files[] = tests/advagg.test configure = admin/config/development/performance/advagg -; Information added by Drupal.org packaging script on 2015-07-18 -version = "7.x-2.14" +; Information added by Drupal.org packaging script on 2016-03-29 +version = "7.x-2.18" core = "7.x" project = "advagg" -datestamp = "1437181444" +datestamp = "1459291143" diff --git a/sites/all/modules/contrib/advagg/advagg.install b/sites/all/modules/contrib/advagg/advagg.install index 6aaa23787..52457fc81 100644 --- a/sites/all/modules/contrib/advagg/advagg.install +++ b/sites/all/modules/contrib/advagg/advagg.install @@ -194,11 +194,24 @@ function advagg_schema() { 'not null' => TRUE, 'default' => 0, ), + 'filesize_processed' => array( + 'description' => 'The file size in bytes after minification and compression.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'use_strict' => array( + 'description' => 'If 1 then the js file starts with "use strict";. If 0 then it does not.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), ), 'indexes' => array( 'content_hash' => array('content_hash'), 'filetype' => array('filetype'), 'filesize' => array('filesize'), + 'use_strict' => array('use_strict'), ), 'primary key' => array('filename_hash'), ); @@ -435,9 +448,10 @@ function advagg_update_7204(&$sandbox) { * usage of ForceType from update 7203. */ function advagg_update_7205(&$sandbox) { - $output = t('First pattern results:') . ' ' . advagg_install_update_htaccess('ForceType text/css .js'); - $output .= ' ' . t('Second pattern results:') . ' ' . advagg_install_update_htaccess('ForceType application/javascript .js'); - return $output; + return t('First pattern results: !first Second pattern results: !second', array( + '!first' => advagg_install_update_htaccess('ForceType text/css .js'), + '!second' => advagg_install_update_htaccess('ForceType application/javascript .js') + )); } /** @@ -552,11 +566,108 @@ function advagg_update_7209(&$sandbox) { return t('Database schema was adjusted to match what is listed in advagg_schema.'); } +/** + * Implements hook_update_N(). + * + * Add filesize_processed field to advagg_files table. + */ +function advagg_update_7210() { + if (!db_field_exists('advagg_files', 'filesize_processed')) { + $spec = array( + 'description' => 'The file size in bytes after minification and compression.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ); + db_add_field('advagg_files', 'filesize_processed', $spec); + } + return t('The filesize_processed field has been added to the advagg_files table.'); +} + +/** + * Implements hook_update_N(). + * + * Populate the filesize_processed field in the advagg_files table. + */ +function advagg_update_7211() { + drupal_load('module', 'advagg'); + module_load_include('inc', 'advagg', 'advagg'); + $types = array('css', 'js'); + foreach ($types as $type) { + $query = db_select('advagg_files', 'af') + ->fields('af') + ->condition('filesize_processed', 0) + ->condition('filetype', $type) + ->execute(); + foreach ($query as $row) { + $row->filesize_processed = (int) advagg_generate_filesize_processed($row->filename, $type); + if (!empty($row->filesize_processed)) { + $write = (array) $row; + db_merge('advagg_files') + ->key(array( + 'filename_hash' => $write['filename_hash'], + )) + ->fields($write) + ->execute(); + } + } + } + return t('The filesize_processed field has been populated inside the advagg_files table.'); +} + +/** + * Implements hook_update_N(). + * + * Add use_strict field to advagg_files table. + */ +function advagg_update_7212() { + if (!db_field_exists('advagg_files', 'use_strict')) { + $spec = array( + 'description' => 'If 1 then the js file starts with "use strict";. If 0 then it does not.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ); + db_add_field('advagg_files', 'use_strict', $spec); + } + if (!db_index_exists('advagg_files', 'use_strict')) { + db_add_index('advagg_files', 'use_strict', array('use_strict')); + } + return t('The use_strict field has been added to the advagg_files table.'); +} + +/** + * Implements hook_update_N(). + * + * Populate the use_strict field in the advagg_files table. + */ +function advagg_update_7213() { + drupal_load('module', 'advagg'); + module_load_include('inc', 'advagg', 'advagg'); + $query = db_select('advagg_files', 'af') + ->fields('af') + ->condition('filetype', 'js') + ->execute(); + foreach ($query as $row) { + $row->use_strict = (int) advagg_does_js_start_with_use_strict($row->filename); + if (!empty($row->use_strict)) { + $write = (array) $row; + db_merge('advagg_files') + ->key(array( + 'filename_hash' => $write['filename_hash'], + )) + ->fields($write) + ->execute(); + } + } + return t('The use_strict field has been populated inside the advagg_files table.'); +} + /** * Convert the table to the specified collation. * * @param string $table_name - * Preform the operation on this table. + * Perform the operation on this table. * @param array $fields * An array of field names. * @param string $collation @@ -596,7 +707,10 @@ function advagg_install_change_table_collation($table_name, array $fields, $coll // $schema_fields[$row->Field]['default'] can be NULL, so we explicitly // check for the key here. - if (array_key_exists('default', $schema_fields[$row->Field])) { + if ( isset($schema_fields[$row->Field]) + && is_array($schema_fields[$row->Field]) + && array_key_exists('default', $schema_fields[$row->Field]) + ) { $default = $schema_fields[$row->Field]['default']; if (is_string($default)) { $default = "'" . $default . "'"; @@ -613,7 +727,7 @@ function advagg_install_change_table_collation($table_name, array $fields, $coll // Add column comment. if (!empty($schema_fields[$row->Field]['description'])) { - $query .= ' COMMENT ' . $db_schema->prepareComment($schema_fields[$row->Field]['description'], $db_schema::COMMENT_MAX_COLUMN); + $query .= ' COMMENT ' . $db_schema->prepareComment($schema_fields[$row->Field]['description'], 255); } db_query($query); @@ -628,7 +742,7 @@ function advagg_install_change_table_collation($table_name, array $fields, $coll */ function advagg_install_delete_empty_file_if_stale($uri) { // Set stale file threshold to 60 seconds. - if (filesize($uri) == 0 && REQUEST_TIME - filemtime($uri) > 60) { + if (filesize($uri) < 3 && REQUEST_TIME - filemtime($uri) > 60) { file_unmanaged_delete($uri); } } @@ -764,7 +878,7 @@ function advagg_requirements($phase) { $config_path = advagg_admin_config_root_path(); // Make sure directories are writable. - if (!file_prepare_directory($css_path[0], FILE_CREATE_DIRECTORY)) { + if (!file_prepare_directory($css_path[0], FILE_CREATE_DIRECTORY + FILE_MODIFY_PERMISSIONS)) { $requirements['advagg_css_path'] = array( 'title' => $t('Adv CSS/JS Agg - CSS Path'), 'severity' => REQUIREMENT_ERROR, @@ -772,7 +886,39 @@ function advagg_requirements($phase) { 'description' => $t('%path is not setup correctly.', array('%path' => $css_path[0])), ); } - if (!file_prepare_directory($js_path[0], FILE_CREATE_DIRECTORY)) { + elseif (!is_writable($css_path[0])) { + $requirements['advagg_css_path'] = array( + 'title' => $t('Adv CSS/JS Agg - CSS Path'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('CSS directory is not writable.'), + 'description' => $t('%path is not setup correctly.', array('%path' => $css_path[0])), + ); + } + elseif (!is_readable($css_path[0])) { + $requirements['advagg_css_path'] = array( + 'title' => $t('Adv CSS/JS Agg - CSS Path'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('CSS directory is not readable.'), + 'description' => $t('%path is not setup correctly.', array('%path' => $css_path[0])), + ); + } + elseif (!is_writable($css_path[1])) { + $requirements['advagg_css_path'] = array( + 'title' => $t('Adv CSS/JS Agg - CSS Path'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('CSS directory is not writable.'), + 'description' => $t('%path is not setup correctly.', array('%path' => $css_path[1])), + ); + } + elseif (!is_readable($css_path[1])) { + $requirements['advagg_css_path'] = array( + 'title' => $t('Adv CSS/JS Agg - CSS Path'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('CSS directory is not readable.'), + 'description' => $t('%path is not setup correctly.', array('%path' => $css_path[1])), + ); + } + if (!file_prepare_directory($js_path[0], FILE_CREATE_DIRECTORY + FILE_MODIFY_PERMISSIONS)) { $requirements['advagg_js_path'] = array( 'title' => $t('Adv CSS/JS Agg - JS Path'), 'severity' => REQUIREMENT_ERROR, @@ -780,6 +926,38 @@ function advagg_requirements($phase) { 'description' => $t('%path is not setup correctly.', array('%path' => $js_path[0])), ); } + elseif (!is_writable($js_path[0])) { + $requirements['advagg_css_path'] = array( + 'title' => $t('Adv CSS/JS Agg - JS Path'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('JS directory is not writable.'), + 'description' => $t('%path is not setup correctly.', array('%path' => $js_path[0])), + ); + } + elseif (!is_readable($js_path[0])) { + $requirements['advagg_css_path'] = array( + 'title' => $t('Adv CSS/JS Agg - JS Path'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('JS directory is not readable.'), + 'description' => $t('%path is not setup correctly.', array('%path' => $js_path[0])), + ); + } + elseif (!is_writable($js_path[1])) { + $requirements['advagg_css_path'] = array( + 'title' => $t('Adv CSS/JS Agg - JS Path'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('JS directory is not writable.'), + 'description' => $t('%path is not setup correctly.', array('%path' => $js_path[1])), + ); + } + elseif (!is_readable($js_path[1])) { + $requirements['advagg_css_path'] = array( + 'title' => $t('Adv CSS/JS Agg - JS Path'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('JS directory is not readable.'), + 'description' => $t('%path is not setup correctly.', array('%path' => $js_path[1])), + ); + } if (!variable_get('advagg_skip_enabled_preprocess_check', ADVAGG_SKIP_ENABLED_PREPROCESS_CHECK)) { // Make sure variables are set correctly. @@ -795,36 +973,26 @@ function advagg_requirements($phase) { $requirements['advagg_core_off'] = array( 'title' => $t('Adv CSS/JS Agg - Core Variables'), 'severity' => REQUIREMENT_ERROR, - 'value' => $t('Cores CSS and/or JS aggregation is disabled.'), + 'value' => $t('Core CSS and/or JS aggregation is disabled.'), 'description' => $t('"Optimize CSS files" and "Optimize JavaScript files" on the performance page should be enabled.', array('@performance' => url('admin/config/development/performance'))), ); } } // Check that the menu router handler is working. - $item = menu_get_item($css_path[1] . '/test.css'); - if (empty($item['page_callback']) || strpos($item['page_callback'], 'advagg') === FALSE) { - $item = str_replace(' ', '    ', nl2br(htmlentities(print_r($item, TRUE)))); + $item_css = menu_get_item($css_path[1] . '/test.css'); + $item_js = menu_get_item($js_path[1] . '/test.js'); + if ( empty($item_css['page_callback']) + || strpos($item_css['page_callback'], 'advagg') === FALSE + || empty($item_js['page_callback']) + || strpos($item_js['page_callback'], 'advagg') === FALSE + ) { $requirements['advagg_async_generation_menu_issue_css'] = array( 'title' => $t('Adv CSS/JS Agg - Async Mode'), - 'severity' => REQUIREMENT_WARNING, - 'value' => $t('Flush your caches.'), - 'description' => $t('You need to flush your menu cache. This can be done at the top of the performance page. If this does not fix the issue copy this info below when opening up an issue for advagg:
!info', array( - '@performance' => url('admin/config/development/performance'), - '!info' => $item, - )), - ); - } - $item = menu_get_item($js_path[1] . '/test.js'); - if (empty($item['page_callback']) || strpos($item['page_callback'], 'advagg') === FALSE) { - $item = str_replace(' ', '    ', nl2br(htmlentities(print_r($item, TRUE)))); - $requirements['advagg_async_generation_menu_issue_js'] = array( - 'title' => $t('Adv CSS/JS Agg - Async Mode'), - 'severity' => REQUIREMENT_WARNING, + 'severity' => REQUIREMENT_ERROR, 'value' => $t('Flush your caches.'), - 'description' => $t('You need to flush your menu cache. This can be done near the top of the performance page under Clear cache. If this does not fix the issue copy this info below when opening up an issue for advagg:
!info', array( + 'description' => $t('You need to flush your menu cache. This can be done at the top of the performance page; under "Clear cache" press the "Clear all caches" button.', array( '@performance' => url('admin/config/development/performance'), - '!info' => $item, )), ); } @@ -942,6 +1110,32 @@ $base_url = str_replace("http://", "https://", $base_url); // Make sure http requests will work correctly. advagg_install_check_via_http($requirements); + // Check that file writes happen without any errors. + if (empty($requirements)) { + module_load_include("missing.inc", "advagg"); + $current_hash = advagg_get_current_hooks_hash(); + $aggregate_settings = advagg_get_hash_settings($current_hash); + $types = array('css', 'js'); + foreach ($types as $type) { + $filename = $type . ADVAGG_SPACE . 'test_write' . REQUEST_TIME . '.' . $type; + $files = array('misc/farbtastic/farbtastic.' . $type => array()); + list($files_to_save, $errors) = advagg_save_aggregate($filename, $files, $type, $aggregate_settings); + foreach ($files_to_save as $uri => $data) { + @unlink($uri); + } + if (!empty($errors)) { + $requirements['advagg_file_write_error_' . $type] = array( + 'title' => $t('Adv CSS/JS Agg - File Write'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('File write had some issues with %type files.', array('%type' => $type)), + 'description' => $t('Most likely there is an issue with file and/or directory premissions. Error: @error', array( + '@error' => print_r($errors, TRUE), + )), + ); + } + } + } + // If all requirements have been met, state advagg should be working. if (empty($requirements)) { $description = ''; @@ -950,7 +1144,7 @@ $base_url = str_replace("http://", "https://", $base_url); $description .= ' ' . $t('Advanced CSS/JS aggregation is disabled. Go to the Advanced CSS/JS aggregation settings page and enable it.', array('@settings' => url($config_path . '/advagg'))); } if (!variable_get('preprocess_css', FALSE) || !variable_get('preprocess_js', FALSE)) { - $description .= ' ' . $t('Cores CSS and/or JS aggregation is disabled. "Optimize CSS files" and "Optimize JavaScript files" on the performance page should be enabled.', array('@performance' => url('admin/config/development/performance'))); + $description .= ' ' . $t('Core CSS and/or JS aggregation is disabled. "Optimize CSS files" and "Optimize JavaScript files" on the performance page should be enabled.', array('@performance' => url('admin/config/development/performance'))); } if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) { @@ -1048,18 +1242,27 @@ function advagg_install_check_via_http(array &$requirements) { } // Try request without https. - if ($request->code == 0 && stripos($request->error, 'Error opening socket ssl://')) { + if ($request->code == 0 && stripos($request->error, 'Error opening socket ssl://') !== FALSE) { $url = advagg_force_http_path($url); $request = drupal_http_request($url, $options); } - // @ignore sniffer_commenting_inlinecomment_spacingbefore:5 + // Try request to 127.0.0.1. + if ($request->code == 0 && stripos($request->error, 'getaddrinfo failed') !== FALSE) { + $parts = @parse_url($url); + if ($parts['host'] !== '127.0.0.1') { + $options['headers']['Host'] = $parts['host']; + $parts['host'] = '127.0.0.1'; + $url = advagg_glue_url($parts); + $request = drupal_http_request($url, $options); + } + } + // Check response. Report an error if // Not a 404 OR // No data returned OR // Headers do not contain "x-advagg" AND // Body does not contain "advagg_missing_fast404". - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace:3 if ( $request->code != 404 || empty($request->data) || ( empty($request->headers['x-advagg']) @@ -1077,11 +1280,29 @@ function advagg_install_check_via_http(array &$requirements) { && !empty($exclude_paths) && strpos($exclude_paths, 'advagg_') === FALSE ) { + $pos_a = strpos($exclude_paths, '(?:styles)'); + $pos_b = strpos($exclude_paths, '(?:styles|'); if ($exclude_paths === '/\/(?:styles)\//') { - $description = $t('Change it from /\/(?:styles)\// to /\/(?:styles|advagg_(cs|j)s)\// Current value: %value', array('%value' => $exclude_paths)); + $description = $t('Change it from %value to /\/(?:styles|advagg_(cs|j)s)\//', array( + '%value' => $exclude_paths, + )); + } + elseif ($pos_a !== FALSE) { + $description = $t('Change it from %value to %code ', array( + '%value' => $exclude_paths, + '%code' => str_replace('(?:styles)', '(?:styles|advagg_(cs|j)s)', $exclude_paths), + )); + } + elseif ($pos_b !== FALSE) { + $description = $t('Change it from %value to %code ', array( + '%value' => $exclude_paths, + '%code' => str_replace('(?:styles|', '(?:styles|advagg_(cs|j)s|', $exclude_paths), + )); } else { - $description = $t('Add in advagg_(cs|j)s into the regex. Current value: %value', array('%value' => $exclude_paths)); + $description = $t('Add in advagg_(cs|j)s into the regex. Current value: %value', array( + '%value' => $exclude_paths, + )); } $requirements['advagg_404_fast_' . $type . '_generation'] = array( 'title' => $t('Adv CSS/JS Agg - Fast 404: HTTP Request'), @@ -1142,13 +1363,54 @@ function advagg_install_check_via_http(array &$requirements) { )), ); } + elseif ($request->code == 403) { + $requirements['advagg_' . $type . '_server_permissions'] = array( + 'title' => $t('Adv CSS/JS Agg - Webserver can not access files'), + 'value' => $t('HTTP requests to advagg for @type files are not getting through.', array('@type' => $type)), + 'severity' => REQUIREMENT_ERROR, + 'description' => $t('Your webserver can not access @type advagg files. This is usually a server permissions issue. Raw request info:
@request
', array( + '@type' => $type, + '@request' => var_export($request, TRUE), + )), + ); + } + elseif (stripos($request->data, 'nginx')) { + $config_location = ''; + if (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') { + $config_location = ' ' . $t('You might be able to find the nginx configuration file by running
@command_1
or
@command_2
', array( + '@command_1' => 'ps -o args -C nginx', + '@command_2' => 'nginx -t', + )); + } + $requirements['advagg_' . $type . '_nginx_config'] = array( + 'title' => $t('Adv CSS/JS Agg - Nginx not sending 404 to Drupal.'), + 'value' => $t('HTTP requests to advagg for @type files are not getting through.', array('@type' => $type)), + 'severity' => REQUIREMENT_ERROR, + 'description' => $t('Your nginx webserver is not sending 404s to drupal. Please make sure that your nginx configuration has something like this in it:

@code

Note that @drupal (last line of code above) might be @rewrite or @rewrites depending on your servers configuration. !config_location Raw request info:
@request
', array( + '@request' => var_export($request, TRUE), + '@code' => nl2br(' +### +### advagg_css and advagg_js support +### +location ~* files/advagg_(?:css|js)/ { + gzip_static on; + access_log off; + expires max; + add_header ETag ""; + add_header Cache-Control "max-age=31449600, no-transform, public"; + try_files $uri @drupal; +}'), + '!config_location' => $config_location, + )), + ); + } else { // Menu callback failed. $requirements['advagg_' . $type . '_generation'] = array( 'title' => $t('Adv CSS/JS Agg - HTTP Request'), 'severity' => REQUIREMENT_ERROR, 'value' => $t('HTTP requests to advagg for ' . $type . ' files are not getting through.'), - 'description' => $t('AdvAgg will issue a request for a file that does not exist inside of the AdvAgg directory. If AdvAgg sends a 404, everything is ok; if something else sends a 404 then that means that AdvAgg will not be able to generate an aggregate if it is missing as something else is handling the 404 before AdvAgg has a chance to do it. If you are reading this, it means that something else is handling the 404 before AdvAgg can. Raw request info:
@request
', array('@request' => print_r($request, TRUE))), + 'description' => $t('AdvAgg will issue a request for a file that does not exist inside of the AdvAgg directory. If AdvAgg sends a 404, everything is ok; if something else sends a 404 then that means that AdvAgg will not be able to generate an aggregate if it is missing as something else is handling the 404 before AdvAgg has a chance to do it. If you are reading this, it means that something else is handling the 404 before AdvAgg can. Raw request info:
@request
', array('@request' => var_export($request, TRUE))), ); } } @@ -1174,7 +1436,7 @@ function advagg_install_check_via_http(array &$requirements) { $file_path = $js_path[1]; } // Get filename. - $filename = advagg_install_get_first_advagg_file($file_path); + $filename = advagg_install_get_first_advagg_file($file_path, $type); // Skip if filename is empty. if (empty($filename)) { @@ -1205,7 +1467,7 @@ function advagg_install_check_via_http(array &$requirements) { ), 'version' => '1.0', ); - // Test http 1.0 + // Test http 1.0. $old_requirements = $requirements; advagg_install_chk_urls($requirements, $urls, $options, $mod_url, $type, $url_path, $file_path, $filename); @@ -1250,7 +1512,16 @@ function advagg_install_chk_urls(array &$requirements, array $urls, array $optio // Ensure translations don't break at install time. $t = get_t(); - foreach ($urls as $key => $url) { + $is_apache = FALSE; + if (stripos($_SERVER['SERVER_SOFTWARE'], 'apache') !== FALSE || function_exists('apache_get_modules')) { + $is_apache = TRUE; + $mod_headers = advagg_install_apache_mod_loaded('mod_headers'); + $mod_rewrite = advagg_install_apache_mod_loaded('mod_rewrite'); + $mod_expires = advagg_install_apache_mod_loaded('mod_expires'); + } + + foreach ($urls as $url) { + $key = pathinfo($url, PATHINFO_EXTENSION); // Make sure the URL contains a schema. if (strpos($url, 'http') !== 0) { if ($GLOBALS['is_https']) { @@ -1265,100 +1536,131 @@ function advagg_install_chk_urls(array &$requirements, array $urls, array $optio advagg_install_url_mod($url, $options, $mod_url); $request = drupal_http_request($url, $options); - // @ignore sniffer_commenting_inlinecomment_spacingbefore:4 - // Check response. Report an error if - // Not a 200. - // Headers do not contain "content-encoding". - // content-encoding is not gzip or deflate. - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace - if ( $request->code != 200 - || empty($request->headers['content-encoding']) - || ( $request->headers['content-encoding'] !== 'gzip' - && $request->headers['content-encoding'] !== 'deflate' - ) - ) { - $config_path = advagg_admin_config_root_path(); - // Gzip failed. - if (!variable_get('advagg_gzip', ADVAGG_GZIP)) { - // Recommend that gzip be turned on. - $requirements['advagg_' . $type . '_gzip' . $key] = array( - 'title' => $t('Adv CSS/JS Agg - gzip'), - 'severity' => REQUIREMENT_WARNING, - 'value' => $t('Gzip is failing for %type files.', array('%type' => $type)), - 'description' => $t('Try enabling on the "Create .gz files" setting on the Advanced CSS/JS Aggregation Configuration page', array( - '@advagg' => url($config_path . '/advagg'), - '%type' => $type, - )), - ); - } - else { - // If the apache_get_modules function doesn't exist, skip this - // entirely. - $apache_module_missing = FALSE; - if (function_exists('apache_get_modules')) { - // Get all available Apache modules. - $modules = apache_get_modules(); - if (!in_array('mod_headers', $modules) || !in_array('mod_rewrite', $modules)) { - $apache_module_missing = TRUE; - - if (!in_array('mod_headers', $modules)) { - $requirements['advagg_mod_headers' . $key] = array( - 'title' => $t('Adv CSS/JS Agg - Apache'), - 'description' => $t('The Apache module "mod_headers" is not available. Enable mod_headers for Apache if at all possible. This is causing gzip to fail.', array('!link' => 'http://httpd.apache.org/docs/current/mod/mod_headers.html')), - 'severity' => REQUIREMENT_WARNING, - 'value' => $t('Apache module "mod_headers" is not installed.'), - ); - } - if (!in_array('mod_rewrite', $modules)) { - $requirements['advagg_mod_rewrite' . $key] = array( - 'title' => $t('Adv CSS/JS Agg - Apache'), - 'description' => $t('The Apache module "mod_rewrite" is not available. You must enable mod_rewrite for Apache. This is causing gzip to fail.', array('!link' => 'http://httpd.apache.org/docs/current/mod/mod_rewrite.html')), - 'severity' => REQUIREMENT_ERROR, - 'value' => $t('Apache module "mod_rewrite" is not installed.'), - ); - } - } - } - if (!$apache_module_missing) { - // Recommend servers configuration be adjusted. - $request->data = '...'; - $requirements['advagg_' . $type . '_gzip' . $key . $options['version']] = array( + if (!variable_get('advagg_skip_gzip_check', ADVAGG_SKIP_GZIP_CHECK)) { + // @ignore sniffer_commenting_inlinecomment_spacingbefore:4 + // Check response. Report an error if + // Not a 200. + // Headers do not contain "content-encoding". + // content-encoding is not gzip or deflate. + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace + if ( $request->code != 200 + || empty($request->headers['content-encoding']) + || ( $request->headers['content-encoding'] !== 'gzip' + && $request->headers['content-encoding'] !== 'deflate' + ) + ) { + $config_path = advagg_admin_config_root_path(); + // Gzip failed. + if (!variable_get('advagg_gzip', ADVAGG_GZIP)) { + // Recommend that gzip be turned on. + $requirements['advagg_' . $type . '_gzip' . $key] = array( 'title' => $t('Adv CSS/JS Agg - gzip'), 'severity' => REQUIREMENT_WARNING, 'value' => $t('Gzip is failing for %type files.', array('%type' => $type)), - 'description' => $t('The web servers configuration will need to be adjusted. In most cases make sure that the webroots .htaccess file still contains this section "Rules to correctly serve gzip compressed CSS and JS files". Certain default web server configurations (nginx) do not gzip HTTP/1.0 requests. If you are using cloudfront you will have to add metadata to the .gz files. There are some other options if using cloudfront. Raw request info:
@request
', array( - '!nginx' => 'http://www.cdnplanet.com/blog/gzip-nginx-cloudfront/', - '@request' => print_r($request, TRUE), - '!cloudfront' => 'http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/ServingCompressedFiles.html#CompressedS3', - '!so' => 'http://stackoverflow.com/questions/5442011/serving-gzipped-css-and-javascript-from-amazon-cloudfront-via-s3', + 'description' => $t('Try enabling on the "Create .gz files" setting on the Advanced CSS/JS Aggregation Configuration page', array( + '@advagg' => url($config_path . '/advagg'), + '%type' => $type, )), ); } + else { + // If not apache skip this. + $apache_module_missing = FALSE; + if ($is_apache) { + if ($mod_headers === FALSE || $mod_rewrite === FALSE) { + $apache_module_missing = TRUE; + + if ($mod_headers === FALSE) { + $requirements['advagg_mod_headers' . $key] = array( + 'title' => $t('Adv CSS/JS Agg - Apache'), + 'description' => $t('The Apache module "mod_headers" is not available. Enable mod_headers for Apache if at all possible. This is causing gzip to fail.', array('!link' => 'http://httpd.apache.org/docs/current/mod/mod_headers.html')), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('Apache module "mod_headers" is not installed.'), + ); + } + if ($mod_rewrite === FALSE) { + $requirements['advagg_mod_rewrite' . $key] = array( + 'title' => $t('Adv CSS/JS Agg - Apache'), + 'description' => $t('The Apache module "mod_rewrite" is not available. You must enable mod_rewrite for Apache. This is causing gzip to fail.', array('!link' => 'http://httpd.apache.org/docs/current/mod/mod_rewrite.html')), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('Apache module "mod_rewrite" is not installed.'), + ); + } + } + } + if (!$apache_module_missing) { + // No data returned. + if (empty($request->data)) { + $requirements['advagg_' . $type . '_gzip' . $key . $options['version']] = array( + 'title' => $t('Adv CSS/JS Agg - gzip'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('Gzip is failing for %type files.', array('%type' => $type)), + 'description' => $t('No data was returned from your server; this gzip test can not be done locally. Error: @error - @message You can manually test if gzip is working by going here and seeing if gzip is enabled. You can turn this warning off by adding this to your settings.php file: @skipcode', array( + '@error' => $request->code, + '@message' => isset($request->error) ? $request->error : '', + '@url' => 'http://checkgzipcompression.com/?url=' . urlencode($url), + '@skipcode' => '$conf[\'advagg_skip_gzip_check\'] = TRUE;', + )), + ); + } + elseif ($request->code != 200) { + $requirements['advagg_' . $type . '_gzip' . $key . $options['version']] = array( + 'title' => $t('Adv CSS/JS Agg - gzip'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('Gzip is failing for %type files.', array('%type' => $type)), + 'description' => $t('The web server is not returning a 200, instead a @code is being returned. Gzip can not be tested. Raw request info:
@request
', array( + '@code' => $request->code, + '@request' => print_r($request, TRUE), + )), + ); + } + else { + // Recommend servers configuration be adjusted. + $request->data = '...'; + $requirements['advagg_' . $type . '_gzip' . $key . $options['version']] = array( + 'title' => $t('Adv CSS/JS Agg - gzip'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('Gzip is failing for %type files.', array('%type' => $type)), + 'description' => $t('The web servers configuration will need to be adjusted. In most cases make sure that the webroots .htaccess file still contains this section "Rules to correctly serve gzip compressed CSS and JS files". Certain default web server configurations (nginx) do not gzip HTTP/1.0 requests. If you are using cloudfront you will have to add metadata to the .gz files. There are some other options if using cloudfront. Raw request info:
@request
', array( + '!nginx' => 'http://www.cdnplanet.com/blog/gzip-nginx-cloudfront/', + '@request' => print_r($request, TRUE), + '!cloudfront' => 'http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/ServingCompressedFiles.html#CompressedS3', + '!so' => 'http://stackoverflow.com/questions/5442011/serving-gzipped-css-and-javascript-from-amazon-cloudfront-via-s3', + )), + ); + } + } + } } } $content_type = $type; if ($content_type === 'js') { $content_type = 'javascript'; } + if ($request->code == 200) { - $modules = array(); $matches = array(); // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace if ( !empty($request->headers['x-advagg']) && preg_match('/Generated file at (\\d+)/is', $request->headers['x-advagg'], $matches) && $matches[1] + 30 > REQUEST_TIME ) { - $requirements['advagg_' . $type . '_loopback_issue' . $key] = array( - 'title' => $t('Adv CSS/JS Agg - Incorrect readings'), - 'severity' => REQUIREMENT_WARNING, - 'value' => $t('The request for a file was served by Drupal instead of the web server (like Apache).', array('%type' => $type)), - 'description' => $t('It means that AdvAgg can not test for Far-Future headers internally and you will need to use an external tool in order to do so. Warnings given or not given about AdvAgg Expires, Cache-Control, and If-Modified-Since might be incorrect. In the readme, under Troubleshooting try the Far-Future recommendations.', array('@readme' => url(drupal_get_path('module', 'advagg') . '/README.txt'))), - ); - } - - if (function_exists('apache_get_modules')) { - // Get all available Apache modules. - $modules = apache_get_modules(); + if (!file_exists($file_path . '/' . $filename)) { + $requirements['advagg_' . $type . '_file_write_' . $key] = array( + 'title' => $t('Adv CSS/JS Agg - Can not write to the filesystem'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('The request for a file was served by Drupal instead of the web server (like Apache).'), + 'description' => $t('Something is preventing writes to your filesystem from working.'), + ); + } + else { + $requirements['advagg_' . $type . '_loopback_issue' . $key] = array( + 'title' => $t('Adv CSS/JS Agg - Incorrect readings'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('The request for a file was served by Drupal instead of the web server (like Apache).'), + 'description' => $t('It means that AdvAgg can not test for Far-Future headers internally and you will need to use an external tool in order to do so. Warnings given or not given about AdvAgg Expires, Cache-Control, and If-Modified-Since might be incorrect. In the readme, under Troubleshooting try the Far-Future recommendations.', array('@readme' => url(drupal_get_path('module', 'advagg') . '/README.txt'))), + ); + } } if ( $type === 'css' @@ -1407,7 +1709,7 @@ function advagg_install_chk_urls(array &$requirements, array $urls, array $optio && strtotime($request->headers['expires']) < time() + 2628000 ) { // Recommend servers configuration be adjusted. - if (function_exists('apache_get_modules') && !in_array('mod_headers', $modules)) { + if ($is_apache && $mod_headers === FALSE) { $apache_module_missing = TRUE; } else { @@ -1415,8 +1717,9 @@ function advagg_install_chk_urls(array &$requirements, array $urls, array $optio 'title' => $t('Adv CSS/JS Agg - Expires'), 'severity' => REQUIREMENT_WARNING, 'value' => $t('The expires header being sent by your web server is not at least 1 month in the future.', array('%type' => $type)), - 'description' => $t('The web servers configuration should be adjusted only for AdvAgg files. Was looking for a second counter over 2,628,000 (1 month), actually got @received. You can turn this warning off if you can not adjust this value (Pantheon) by adding this to your settings.php file: @skipcode', array( - '@received' => isset($request->headers['expires']) ? number_format(strtotime($request->headers['expires'])) : 'NULL', + 'description' => $t('The web servers configuration should be adjusted only for AdvAgg files. Was looking for a second counter over 2,628,000 (1 month), actually got @received (@expires). You can turn this warning off if you can not adjust this value (Pantheon) by adding this to your settings.php file: @skipcode', array( + '@received' => isset($request->headers['expires']) ? number_format(strtotime($request->headers['expires']) - REQUEST_TIME) : 'NULL', + '@expires' => isset($request->headers['expires']) ? $request->headers['expires'] : 'NULL', '@skipcode' => '$conf[\'advagg_skip_far_future_check\'] = TRUE;', )), ); @@ -1430,7 +1733,7 @@ function advagg_install_chk_urls(array &$requirements, array $urls, array $optio || $matches[1] < 2628000 ) { // Recommend servers configuration be adjusted. - if (function_exists('apache_get_modules') && (!in_array('mod_headers', $modules))) { + if ($is_apache && $mod_headers === FALSE) { $apache_module_missing = TRUE; } else { @@ -1440,7 +1743,7 @@ function advagg_install_chk_urls(array &$requirements, array $urls, array $optio 'value' => $t("The cache-control's max-age header being sent by your web server is not at least 1 month in the future.", array('%type' => $type)), 'description' => $t('The web servers configuration should be adjusted only for AdvAgg files. Was looking that the max-age second counter is over 2,628,000 (1 month), actually got @received. You can turn this warning off if you can not adjust this value (Pantheon) by adding this to your settings.php file: @skipcode', array( '@received' => isset($request->headers['cache-control']) ? $request->headers['cache-control'] : 'NULL', - '@skipcode' => '$conf[\'advagg_skip_far_future_check\'] = FALSE;', + '@skipcode' => '$conf[\'advagg_skip_far_future_check\'] = TRUE;', )), ); } @@ -1448,16 +1751,25 @@ function advagg_install_chk_urls(array &$requirements, array $urls, array $optio } // Handle missing apache modules. - if ($apache_module_missing && !in_array('mod_headers', $modules)) { - $requirements['advagg_mod_headers_far_future' . $key] = array( + if ($apache_module_missing && $is_apache && $mod_headers === FALSE) { + $requirements['advagg_mod_headers_far_future_headers_' . $key] = array( 'title' => $t('Adv CSS/JS Agg - Apache'), 'description' => $t('The Apache module "mod_headers" is not available. Enable mod_headers for Apache if at all possible. This is causing far-future headers to not be sent correctly.', array('!link' => 'http://httpd.apache.org/docs/current/mod/mod_headers.html')), 'severity' => REQUIREMENT_WARNING, 'value' => $t('Apache module "mod_headers" is not installed.'), ); + if ($mod_expires === FALSE) { + $requirements['advagg_mod_headers_far_future_expires_' . $key] = array( + 'title' => $t('Adv CSS/JS Agg - Apache'), + 'description' => $t('The Apache module "mod_expires" is not available. Enable mod_expires for Apache if at all possible. This is causing far-future headers to not be sent correctly.', array('!link' => 'http://httpd.apache.org/docs/current/mod/mod_expires.html')), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('Apache module "mod_headers" is not installed.'), + ); + } } // Test 304. + $etag_works = FALSE; if (isset($request->headers['etag'])) { // Send an Etag header and see if the web server returns a 304. $url = file_create_url($url_path . '/' . $filename); @@ -1481,6 +1793,9 @@ function advagg_install_chk_urls(array &$requirements, array $urls, array $optio )), ); } + else { + $etag_works = TRUE; + } } if (isset($request->headers['last-modified'])) { // Send a If-Modified-Since header and see if the web server returns a @@ -1512,6 +1827,11 @@ function advagg_install_chk_urls(array &$requirements, array $urls, array $optio unset($requirements['advagg_' . $type . '_304' . $key . '_etag']); } } + if ($etag_works && isset($requirements['advagg_' . $type . '_304' . $key . '_last_modified'])) { + // Etag works, Last-Modified is broken. 304s are working. Don't warn + // user. + unset($requirements['advagg_' . $type . '_304' . $key . '_last_modified']); + } // Both the Last-Modified and Etag header are missing. if (empty($request->headers['last-modified']) && empty($request->headers['etag'])) { @@ -1563,11 +1883,13 @@ function advagg_install_chk_urls(array &$requirements, array $urls, array $optio * * @param string $directory * Path to the advagg css/js dir. + * @param string $type + * String: css or js. * * @return string * Returns aggregate filename or an empty string on failure. */ -function advagg_install_get_first_advagg_file($directory) { +function advagg_install_get_first_advagg_file($directory, $type) { module_load_include('inc', 'advagg', 'advagg.missing'); $filename = ''; @@ -1575,7 +1897,8 @@ function advagg_install_get_first_advagg_file($directory) { $scanned_directory = @scandir($directory); // Bailout here if the advagg directory is empty. if (empty($scanned_directory)) { - return $filename; + // Get a file that will generate from the database. + return advagg_install_generate_advagg_filename_from_db($type); } // Filter list. $blacklist = array('..', '.', '.htaccess', 'parts'); @@ -1585,7 +1908,7 @@ function advagg_install_get_first_advagg_file($directory) { $scanned_directory[] = ''; foreach ($scanned_directory as $key => $filename) { - // Skip if filename is not long enough, + // Skip if filename is not long enough. $len = strlen($filename); if ($len < 91 + strlen(ADVAGG_SPACE) * 3) { continue; @@ -1628,6 +1951,9 @@ function advagg_install_get_first_advagg_file($directory) { } } + if (empty($filename)) { + return advagg_install_generate_advagg_filename_from_db($type); + } return $filename; } @@ -1660,16 +1986,39 @@ function advagg_install_url_mod(&$url, array &$options, $mod_url = FALSE) { return; } + // Check if this is a protocol relative url, if so add a artificial scheme so + // that advagg_glue_url() will produce a proper absolute url. That will work + // with drupal_http_request(). + if (!isset($parts['scheme']) && substr($url, 0, 2) == '//') { + global $base_url; + $parts['scheme'] = @parse_url($base_url, PHP_URL_SCHEME); + } // Pass along user/pass in the URL. $advagg_auth_basic_user = variable_get('advagg_auth_basic_user', ADVAGG_AUTH_BASIC_USER); if (module_exists('shield')) { $parts['user'] = variable_get('shield_user', ''); $parts['pass'] = variable_get('shield_pass', ''); } - elseif (isset($_SERVER['AUTH_TYPE']) && $_SERVER['AUTH_TYPE'] == 'Basic') { + elseif ( isset($_SERVER['AUTH_TYPE']) + && $_SERVER['AUTH_TYPE'] == 'Basic' + ) { $parts['user'] = $_SERVER['PHP_AUTH_USER']; $parts['pass'] = $_SERVER['PHP_AUTH_PW']; } + elseif ( isset($_SERVER['HTTP_AUTHORIZATION']) + && strpos($_SERVER['HTTP_AUTHORIZATION'], 'Basic ') === 0 + ) { + $user_pass = explode(':', base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 5))); + $parts['user'] = $user_pass[0]; + $parts['pass'] = $user_pass[1]; + } + elseif ( isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION']) + && strpos($_SERVER['REDIRECT_HTTP_AUTHORIZATION'], 'Basic ') === 0 + ) { + $user_pass = explode(':', base64_decode(substr($_SERVER['REDIRECT_HTTP_AUTHORIZATION'], 5))); + $parts['user'] = $user_pass[0]; + $parts['pass'] = $user_pass[1]; + } elseif (!empty($advagg_auth_basic_user)) { $parts['user'] = $advagg_auth_basic_user; $parts['pass'] = variable_get('advagg_auth_basic_pass', ADVAGG_AUTH_BASIC_PASS); @@ -1682,14 +2031,20 @@ function advagg_install_url_mod(&$url, array &$options, $mod_url = FALSE) { $new_url = httprl_build_url_self($path); } else { - if (!empty($_SERVER['HTTP_HOST']) && $parts['host'] != $_SERVER['HTTP_HOST']) { - $parts['host'] = $_SERVER['HTTP_HOST']; + if (!empty($_SERVER['HTTP_HOST'])) { + if ($parts['host'] != $_SERVER['HTTP_HOST']) { + $parts['host'] = $_SERVER['HTTP_HOST']; + } } - elseif (!empty($_SERVER['SERVER_NAME']) && $parts['host'] != $_SERVER['SERVER_NAME']) { - $parts['host'] = $_SERVER['SERVER_NAME']; + elseif (!empty($_SERVER['SERVER_NAME'])) { + if ($parts['host'] != $_SERVER['SERVER_NAME']) { + $parts['host'] = $_SERVER['SERVER_NAME']; + } } - elseif (!empty($_SERVER['SERVER_ADDR']) && $parts['host'] != $_SERVER['SERVER_ADDR']) { - $parts['host'] = $_SERVER['SERVER_ADDR']; + elseif (!empty($_SERVER['SERVER_ADDR'])) { + if ($parts['host'] != $_SERVER['SERVER_ADDR']) { + $parts['host'] = $_SERVER['SERVER_ADDR']; + } } else { $parts['host'] = '127.0.0.1'; @@ -1702,3 +2057,76 @@ function advagg_install_url_mod(&$url, array &$options, $mod_url = FALSE) { } $url = $new_url; } + +/** + * Given a advagg type this will return the most recent aggregate from the db. + * + * @param string $type + * String: css or js. + * + * @return string + * Returns advagg filename or an empty string on failure. + */ +function advagg_install_generate_advagg_filename_from_db($type) { + // Get the most recently accessed file from the database. + $query = db_select('advagg_aggregates_versions', 'aav'); + $query->join('advagg_aggregates', 'aa', 'aa.aggregate_filenames_hash = + aav.aggregate_filenames_hash'); + $query->join('advagg_files', 'af', 'af.filename_hash = aa.filename_hash AND + af.filetype = :type', array(':type' => $type)); + $query = $query->fields('aav', array('aggregate_filenames_hash', 'aggregate_contents_hash')) + ->orderBy('atime', 'DESC') + ->range(0, 1); + $results = $query->execute(); + if (empty($results)) { + return ''; + } + $hooks_hash = advagg_get_current_hooks_hash(); + foreach ($results as $row) { + return $type . ADVAGG_SPACE . $row->aggregate_filenames_hash . ADVAGG_SPACE . $row->aggregate_contents_hash . ADVAGG_SPACE . $hooks_hash . '.' . $type; + } +} + + +/** + * Checks to see if an apache module is enabled. + * + * @param string $mod + * Name of an apache module. + * + * @return bool + * TRUE if it exists; FALSE if it does not; NULL if it can not be determined. + */ +function advagg_install_apache_mod_loaded($mod) { + $sapi_type = php_sapi_name(); + if (substr($sapi_type, 0, 3) == 'cgi' || $sapi_type == 'fpm-fcgi') { + // NULL returned, apache_get_modules and phpinfo can not be called. + return NULL; + } + if (is_callable('apache_get_modules')) { + $mods = apache_get_modules(); + if (in_array($mod, $mods)) { + // Return TRUE, module exists. + return TRUE; + } + } + elseif (is_callable('phpinfo') && FALSE === strpos(ini_get('disable_functions'), 'phpinfo')) { + // Use static so we don't run phpinfo mutiple times. + $phpinfo = &drupal_static(__FUNCTION__); + if (empty($phpinfo)) { + // Use phpinfo to get the info if apache_get_modules doesn't exist. + ob_start(); + phpinfo(8); + $phpinfo = ob_get_clean(); + } + if (FALSE !== strpos($phpinfo, $mod)) { + // Return TRUE, module exists. + return TRUE; + } + } + else { + // NULL returned, apache_get_modules and phpinfo can not be called. + return NULL; + } + return FALSE; +} diff --git a/sites/all/modules/contrib/advagg/advagg.missing.inc b/sites/all/modules/contrib/advagg/advagg.missing.inc index 2e1ccfc9a..cf0b6acf2 100644 --- a/sites/all/modules/contrib/advagg/advagg.missing.inc +++ b/sites/all/modules/contrib/advagg/advagg.missing.inc @@ -210,6 +210,12 @@ function advagg_generate_location_uri($filename, $type, array $aggregate_setting * Array of settings. Used to generate the 307 redirect location. */ function advagg_missing_send_saved_file(array $files_to_save, $uri, $created, $filename, $type, $redirect_counter, array $aggregate_settings = array()) { + // Send a 304 if this is a repeat request. + if (!empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) >= REQUEST_TIME) { + header("HTTP/1.1 304 Not Modified"); + exit(); + } + // Negotiate whether to use gzip compression. $return_compressed = isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE; header('Vary: Accept-Encoding', FALSE); @@ -253,6 +259,19 @@ function advagg_missing_send_saved_file(array $files_to_save, $uri, $created, $f // Output file and exit. if (!empty($data)) { + $strlen = strlen($data); + // Send a 304 if this is a repeat request. + if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) { + $etags = explode(' ', $_SERVER['HTTP_IF_NONE_MATCH']); + if ( $etags[0] < REQUEST_TIME + 31 * 24 * 60 * 60 + && isset($etags[1]) + && $etags[1] == $strlen + ) { + header("HTTP/1.1 304 Not Modified"); + exit(); + } + } + // Send out a 200 OK status. header($_SERVER['SERVER_PROTOCOL'] . " 200 OK"); @@ -263,6 +282,13 @@ function advagg_missing_send_saved_file(array $files_to_save, $uri, $created, $f // Etags generation in php is broken due to millisecond precision for the // files mtime; apache has it, php does not. } + else { + header('Last-Modified: ' . gmdate('D, d M Y H:i:s \G\M\T', REQUEST_TIME)); + } + // Set the Expires date 1 month into the future. + header('Expires: ' . gmdate('D, d M Y H:i:s \G\M\T', REQUEST_TIME + 31 * 24 * 60 * 60)); + // Also send an etag out. + header('Etag: ' . REQUEST_TIME . ' ' . $strlen); if ($type === 'css') { header("Content-Type: text/css"); @@ -294,7 +320,7 @@ function advagg_missing_set_farfuture_headers() { // Browsers that implement the W3C Access Control specification might refuse // to use certain resources such as fonts if those resources violate the // same-origin policy. Send a header to explicitly allow cross-domain use of - // those resources. (This is called Cross-Origin Resource Sharing, or CORS.) + // those resources. This is called Cross-Origin Resource Sharing, or CORS. header("Access-Control-Allow-Origin: *"); // Remove all previously set Cache-Control headers, because we're going to // override it. Since multiple Cache-Control headers might have been set, @@ -356,7 +382,7 @@ function advagg_missing_create_file($filename, $no_alters = FALSE, $data = array } // Save aggregate file. - $files_to_save = advagg_save_aggregate($filename, $files, $type, $aggregate_settings); + list($files_to_save, $errors) = advagg_save_aggregate($filename, $files, $type, $aggregate_settings); // Update atime. advagg_multi_update_atime(array( array( @@ -377,6 +403,7 @@ function advagg_missing_create_file($filename, $no_alters = FALSE, $data = array $aggregate_contents_hash, $aggregate_settings, $files, + $errors, ); } @@ -389,6 +416,10 @@ function advagg_missing_create_file($filename, $no_alters = FALSE, $data = array * String: css or js. * @param bool $force * (Optional) force recreate the .htaccess file. + * + * @return array + * Empty array if not errors happened, list of errors if the write had any + * issues. */ function advagg_htaccess_check_generate(array $files_to_save, $type, $force = FALSE) { $content_type = $type; @@ -399,16 +430,16 @@ function advagg_htaccess_check_generate(array $files_to_save, $type, $force = FA // @ignore style_lowercase_html:45 $data = "\n"; $data .= "\n"; - $data .= " # No mod_headers\n"; + $data .= " # No mod_headers. Apache module headers is not enabled.\n"; $data .= " \n"; - $data .= " # No mod_expires\n"; + $data .= " # No mod_expires. Apache module expires is not enabled.\n"; $data .= " \n"; $data .= " # Use ETags.\n"; $data .= " FileETag MTime Size\n"; $data .= " \n"; $data .= " \n"; $data .= "\n"; - $data .= " # Use Expires Directive.\n"; + $data .= " # Use Expires Directive if apache module expires is enabled.\n"; $data .= " \n"; $data .= " # Do not use ETags.\n"; $data .= " FileETag None\n"; @@ -418,6 +449,7 @@ function advagg_htaccess_check_generate(array $files_to_save, $type, $force = FA $data .= " ExpiresDefault A31449600\n"; $data .= " \n"; $data .= "\n"; + $data .= " # Use Headers Directive if apache module headers is enabled.\n"; $data .= " \n"; $data .= " # Do not use etags for cache validation.\n"; $data .= " Header unset ETag\n"; @@ -444,6 +476,7 @@ function advagg_htaccess_check_generate(array $files_to_save, $type, $force = FA } $data .= "\n"; + $errors = array(); foreach (array_keys($files_to_save) as $uri) { $dir = dirname($uri); $htaccess_file = $dir . '/.htaccess'; @@ -451,8 +484,9 @@ function advagg_htaccess_check_generate(array $files_to_save, $type, $force = FA continue; } - advagg_save_data($htaccess_file, $data, $force); + $errors = advagg_save_data($htaccess_file, $data, $force); } + return $errors; } // Lookup functions. @@ -562,11 +596,14 @@ function advagg_get_files_from_hashes($type, $aggregate_filenames_hash, $aggrega * Array of filenames. * @param array $aggregate_settings * Array of settings. + * @param string $aggregate_filename + * Filename of the aggregeate. * * @return string * String containing all the files. */ -function advagg_get_css_aggregate_contents(array $files, array $aggregate_settings) { +function advagg_get_css_aggregate_contents(array $files, array $aggregate_settings, $aggregate_filename = '') { + $write_aggregate = TRUE; // Check if CSS compression is enabled. $optimize = TRUE; if (!empty($aggregate_settings['settings']['no_alters'])) { @@ -576,6 +613,9 @@ function advagg_get_css_aggregate_contents(array $files, array $aggregate_settin $optimize = FALSE; } + module_load_include('inc', 'advagg', 'advagg'); + $info_on_files = advagg_load_files_info_into_static_cache(array_keys($files)); + $data = ''; if (!empty($files)) { $media_changes = FALSE; @@ -633,7 +673,31 @@ function advagg_get_css_aggregate_contents(array $files, array $aggregate_settin if (empty($aggregate_settings['settings']['no_alters'])) { drupal_alter('advagg_get_css_file_contents_pre', $file, $optimize, $aggregate_settings); } - $contents = advagg_load_css_stylesheet($file, $optimize, $aggregate_settings); + if (is_readable($file)) { + // Get the files contents. + $file_contents = (string) @file_get_contents($file); + // Get a hash of the file's contents. + $file_contents_hash = drupal_hash_base64($file_contents); + $cid = 'advagg:file:' . advagg_drupal_hash_base64($file); + if (empty($info_on_files[$cid]['content_hash'])) { + // If hash was not in the cache, get it from the DB. + $results = db_select('advagg_files', 'af') + ->fields('af', array('content_hash', 'filename_hash')) + ->condition('filename', $file) + ->execute(); + foreach ($results as $row) { + $info_on_files['advagg:file:' . $row->filename_hash]['content_hash'] = $row->content_hash; + } + } + if ($info_on_files[$cid]['content_hash'] !== $file_contents_hash) { + // If the content hash doesn't match don't write the file. + $write_aggregate = advagg_missing_file_not_readable($file, $aggregate_filename, FALSE); + } + $contents = advagg_load_css_stylesheet($file, $optimize, $aggregate_settings, $file_contents); + } + else { + $write_aggregate = advagg_missing_file_not_readable($file, $aggregate_filename, TRUE); + } // Allow other modules to modify this files contents. // Call hook_advagg_get_css_file_contents_alter(). @@ -696,18 +760,32 @@ function advagg_get_css_aggregate_contents(array $files, array $aggregate_settin $css_selectors_rules = $css_rules; } $media_rules = trim($media_rules); - $css_selectors_rules = trim($css_selectors_rules); + + // Pul all @font-face defentions inside the @media declaration above. + $font_face_string = ''; + $font_blocks = advagg_parse_media_blocks($css_selectors_rules, '@font-face'); + $css_selectors_rules = ''; + foreach ($font_blocks as $rules) { + if (strpos($rules, '@font-face') !== FALSE) { + $font_face_string .= "\n {$rules}"; + } + else { + $css_selectors_rules .= $rules; + } + } + $css_selectors_rules = str_replace("\n", "\n ", $css_selectors_rules); + $font_face_string = str_replace("\n", "\n ", $font_face_string); // Wrap css in dedicated media query if it differs from the global // media query and there actually are media rules. if (!empty($media_rules) && $media_rules !== $global_file_media) { - $output = "\n@media " . $media_rules . " {\n " . str_replace("\n", "\n ", $css_selectors_rules) . "\n}"; + $output = "{$font_face_string} \n@media {$media_rules} {\n {$css_selectors_rules} \n}"; } else { - $output = "\n" . $css_selectors_rules; + $output = "{$font_face_string} \n {$css_selectors_rules}"; } - $contents .= $output; + $contents .= trim($output); } } @@ -723,7 +801,7 @@ function advagg_get_css_aggregate_contents(array $files, array $aggregate_settin $import_statements[] = array($import_media, $matches[0]); // Close any open comment blocks. - $contents .= '/**/'; + $contents .= "/**/\n"; if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) { $contents .= "\n/* Above code came from $file */\n\n"; } @@ -751,7 +829,7 @@ function advagg_get_css_aggregate_contents(array $files, array $aggregate_settin if (empty($aggregate_settings['settings']['no_alters'])) { drupal_alter('advagg_get_css_aggregate_contents', $data, $files, $aggregate_settings); } - return $data; + return array($data, $write_aggregate); } /** @@ -761,25 +839,58 @@ function advagg_get_css_aggregate_contents(array $files, array $aggregate_settin * Array of filenames. * @param array $aggregate_settings * Array of settings. + * @param string $aggregate_filename + * Filename of the aggregeate. * * @return string * String containing all the files. */ -function advagg_get_js_aggregate_contents(array $files, array $aggregate_settings) { +function advagg_get_js_aggregate_contents(array $files, array $aggregate_settings, $aggregate_filename = '') { + $write_aggregate = TRUE; $data = ''; + module_load_include('inc', 'advagg', 'advagg'); + $info_on_files = advagg_load_files_info_into_static_cache(array_keys($files)); + if (!empty($files)) { // Build aggregate JS file. foreach ($files as $filename => $settings) { $contents = ''; // Append a ';' and a newline after each JS file to prevent them from // running together. Also close any comment blocks. - if (file_exists($filename)) { - $contents .= file_get_contents($filename) . ";/**/\n"; + if (is_readable($filename)) { + $file_contents = (string) @file_get_contents($filename); + $file_contents_hash = drupal_hash_base64($file_contents); + $cid = 'advagg:file:' . advagg_drupal_hash_base64($filename); + if (empty($info_on_files[$cid]['content_hash'])) { + $results = db_select('advagg_files', 'af') + ->fields('af', array('content_hash', 'filename_hash')) + ->condition('filename', $filename) + ->execute(); + foreach ($results as $row) { + $info_on_files['advagg:file:' . $row->filename_hash]['content_hash'] = $row->content_hash; + } + } + if ($info_on_files[$cid]['content_hash'] !== $file_contents_hash) { + // If the content hash doesn't match don't write the file. + $write_aggregate = advagg_missing_file_not_readable($filename, $aggregate_filename, FALSE); + } + + $contents .= $file_contents; + // If a BOM is found, convert the file to UTF-8. + $encoding = advagg_get_encoding_from_bom($contents); + if (!empty($encoding)) { + $contents = advagg_convert_to_utf8($contents, $encoding); + } + // Make sure that the file is ended properly. + $contents .= ";/**/\n"; if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) { $contents .= "/* Above code came from $filename */\n\n"; } } + else { + $write_aggregate = advagg_missing_file_not_readable($filename, $aggregate_filename, TRUE); + } // Allow other modules to modify this files contents. // Call hook_advagg_get_js_file_contents_alter(). if (empty($aggregate_settings['settings']['no_alters'])) { @@ -794,7 +905,55 @@ function advagg_get_js_aggregate_contents(array $files, array $aggregate_setting if (empty($aggregate_settings['settings']['no_alters'])) { drupal_alter('advagg_get_js_aggregate_contents', $data, $files, $aggregate_settings); } - return $data; + return array($data, $write_aggregate); +} + +/** + * Let other modules know that this. + * + * @param string $filename + * Filename of the missing file. + * @param string $aggregate_filename + * Filename of the aggregate that is trying to be generated. + * @param bool $fs_read_failure + * Set to TRUE if the file system couldn't be read. + */ +function advagg_missing_file_not_readable($filename, $aggregate_filename = '', $fs_read_failure = FALSE) { + $write_aggregate = FALSE; + $config_path = advagg_admin_config_root_path(); + + // Get cache of this report. + $cid = 'advagg:file_issue:' . drupal_hash_base64($filename); + $cache = cache_get($cid, 'cache_advagg_info'); + + // Let other modules know about this missing file. + // Call hook_advagg_missing_root_file(). + module_invoke_all('advagg_missing_root_file', $aggregate_filename, $filename, $cache); + + // Report to watchdog if this is not cached and it's not in the public dir. + if (empty($cache) && strpos($filename, 'public://') !== 0) { + if ($fs_read_failure) { + watchdog('advagg', 'Reading from the file system failed. This can sometimes happen durning a deployment. %file %aggregate', array( + '%file' => $filename, + '%aggregate' => $aggregate_filename, + ), WATCHDOG_WARNING); + } + else { + watchdog('advagg', 'The content hash for %file does not match the stored content hash from the database. Please flush the advagg cache under Smart Cache Flush. This can sometimes happen durning a deployment. %file %aggregate', array( + '%file' => $filename, + '%aggregate' => $aggregate_filename, + '@url' => url($config_path . '/advagg/operations', array( + 'fragment' => 'edit-smart-flush', + )), + ), WATCHDOG_WARNING); + } + cache_set($cid, TRUE, 'cache_advagg_info', CACHE_TEMPORARY); + } + elseif (!empty($cache) && $cache->created < (REQUEST_TIME - variable_get('advagg_file_read_failure_timeout', ADVAGG_FILE_READ_FAILURE_TIMEOUT))) { + // Write the aggregate if it's been in a failure state for over an hour. + $write_aggregate = TRUE; + } + return $write_aggregate; } // File save functions. @@ -811,7 +970,7 @@ function advagg_get_js_aggregate_contents(array $files, array $aggregate_setting * Array of settings. * * @return array - * $files_to_save array. + * array($files_to_save, $errors). */ function advagg_save_aggregate($filename, array $files, $type, array $aggregate_settings) { list($css_path, $js_path) = advagg_get_root_files_dir(); @@ -832,10 +991,10 @@ function advagg_save_aggregate($filename, array $files, $type, array $aggregate_ // Build the aggregates contents. $contents = ''; if ($type === 'css') { - $contents = advagg_get_css_aggregate_contents($files, $aggregate_settings); + list($contents, $write_aggregate) = advagg_get_css_aggregate_contents($files, $aggregate_settings, $filename); } elseif ($type === 'js') { - $contents = advagg_get_js_aggregate_contents($files, $aggregate_settings); + list($contents, $write_aggregate) = advagg_get_js_aggregate_contents($files, $aggregate_settings, $filename); } if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) { $contents = "/* This aggregate contains the following files:\n" . implode(",\n", array_keys($files)) . ". */\n\n" . $contents; @@ -853,33 +1012,36 @@ function advagg_save_aggregate($filename, array $files, $type, array $aggregate_ drupal_alter('advagg_save_aggregate', $files_to_save, $aggregate_settings, $other_parameters); } - foreach ($files_to_save as $uri => $data) { - advagg_save_data($uri, $data); - if (!file_exists($uri) || filesize($uri) == 0) { - if ($type === 'css') { - $full_dir = DRUPAL_ROOT . '/' . $css_path[1]; - } - elseif ($type === 'js') { - $full_dir = DRUPAL_ROOT . '/' . $js_path[1]; - } - $free_space = @disk_free_space($full_dir); - if ($free_space !== FALSE && strlen($data) > $free_space) { - watchdog('advagg', 'Write to file system failed. Disk is full. %uri', array('%uri' => $uri), WATCHDOG_ALERT); - } - elseif (!is_writable($full_dir)) { - watchdog('advagg', 'Write to file system failed. Check directory permissions. %uri', array('%uri' => $uri), WATCHDOG_ERROR); - } - else { - watchdog('advagg', 'Write to file system failed. %uri', array('%uri' => $uri), WATCHDOG_ERROR); - } - // If the file is empty, remove it. Serving via drupal is better than an - // empty aggregate being served. - if (file_exists($uri) && filesize($uri) == 0) { - @unlink($uri); + $errors = array(); + if ($write_aggregate) { + foreach ($files_to_save as $uri => $data) { + $errors = advagg_save_data($uri, $data); + if (!file_exists($uri) || filesize($uri) == 0) { + if ($type === 'css') { + $full_dir = DRUPAL_ROOT . '/' . $css_path[1]; + } + elseif ($type === 'js') { + $full_dir = DRUPAL_ROOT . '/' . $js_path[1]; + } + $free_space = @disk_free_space($full_dir); + if ($free_space !== FALSE && strlen($data) > $free_space) { + watchdog('advagg', 'Write to file system failed. Disk is full. %uri', array('%uri' => $uri), WATCHDOG_ALERT); + } + elseif (!is_writable($full_dir)) { + watchdog('advagg', 'Write to file system failed. Check directory permissions. %uri', array('%uri' => $uri), WATCHDOG_ERROR); + } + else { + watchdog('advagg', 'Write to file system failed. %uri', array('%uri' => $uri), WATCHDOG_ERROR); + } + // If the file is empty, remove it. Serving via drupal is better than an + // empty aggregate being served. + if (file_exists($uri) && filesize($uri) == 0) { + @unlink($uri); + } } } } - return $files_to_save; + return array($files_to_save, $errors); } /** @@ -894,14 +1056,21 @@ function advagg_save_aggregate($filename, array $files, $type, array $aggregate_ * A string containing the contents of the file. * @param bool $overwrite * (optional) Bool, set to TRUE to overwrite a file. + * + * @return array + * Empty array if not errors happened, list of errors if the write had any + * issues. */ function advagg_save_data($uri, $data, $overwrite = FALSE) { + $t = get_t(); + $errors = array(); // Clear the stat cache. module_load_include('inc', 'advagg', 'advagg'); advagg_clearstatcache($uri); // File already exists. if (!$overwrite && file_exists($uri) && filesize($uri) > 0) { - return; + $errors[2] = $t('File (@file) already exits.', array('@file' => $uri)); + return $errors; } // If data is empty, write a space. @@ -916,10 +1085,73 @@ function advagg_save_data($uri, $data, $overwrite = FALSE) { // the rename command stays local. // // Get a temporary filename in the destination directory. - $dir = drupal_dirname($uri) . '/'; - $temporary_file = @drupal_tempnam($dir, 'file_advagg_'); + $uri_dir = drupal_dirname($uri) . '/'; + + // Corect the bug with drupal_tempnam where it doesn't pass subdirs to + // tempnam() if the dir is a stream wrapper. + $scheme = file_uri_scheme($uri_dir); + if ($scheme && file_stream_wrapper_valid_scheme($scheme)) { + $wrapper = file_stream_wrapper_get_instance_by_scheme($scheme); + $dir = $wrapper->getDirectoryPath() . '/' . substr($uri_dir, strlen($scheme . '://')); + $uri = $dir . substr($uri, strlen($uri_dir)); + } + else { + $dir = $uri_dir; + } + + $temporary_file = @drupal_tempnam($dir, 'advagg_file_'); if ($temporary_file === FALSE) { - $temporary_file = $dir . 'file_advagg_' . md5(getmypid() . microtime(TRUE) . mt_rand()); + $temporary_file = $dir . 'advagg_file_' . md5(getmypid() . microtime(TRUE) . mt_rand()); + $errors[4] = $t('Temp file was not created using drupal_tempnam(); using @temp instead', array('@temp' => $temporary_file)); + } + // Make sure temp filename has the correct name. + if (strpos($temporary_file, 'advagg_file_') === FALSE) { + // On windows, tempnam only takes the first 3 characters of the prefix. + if (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') { + $errors[6] = $t('Temp file (@file) did not have the correct name (starting with "advagg_file_") when using drupal_tempnam().', array('@file' => $temporary_file)); + } + + $basename = basename($temporary_file); + $temporary_file_fixed = str_replace($basename, 'advagg_file_' . md5(getmypid() . microtime(TRUE) . mt_rand()), $temporary_file); + if (!@rename($temporary_file, $temporary_file_fixed)) { + $errors[8] = $t('Renaming the incorrect temp filename (@incorrect) to the correct temp filename (@correct) failed.', array('@incorrect' => $temporary_file, '@correct' => $temporary_file_fixed)); + // Remove if rename failed. + @unlink($temporary_file); + if (file_exists($temporary_file)) { + $errors[10] = $t('unlinking @file failed.', array('@file' => $temporary_file)); + } + watchdog('advagg', 'Rename 1 failed. Current: %current Target: %target', array( + '%current' => $temporary_file, + '%target' => $temporary_file_fixed, + ), WATCHDOG_ERROR); + } + else { + $temporary_file = $temporary_file_fixed; + } + } + // Make sure temp filename has the correct dir. + $subdir = basename($dir); + if (strpos($temporary_file, $subdir) === FALSE) { + $errors[12] = $t('Temp file (@temp) did not have the correct directory (@dir) when using drupal_tempnam().', array('@temp' => $temporary_file, '@dir' => $subdir)); + + $basename = basename($temporary_file); + $baddir = substr($temporary_file, 0, strpos($temporary_file, $basename)); + $temporary_file_fixed = str_replace($baddir, $dir, $temporary_file); + if (!@rename($temporary_file, $temporary_file_fixed)) { + $errors[14] = $t('Renaming the incorrect temp filename (@incorrect) to the correct temp filename (@correct) failed.', array('@incorrect' => $temporary_file, '@correct' => $temporary_file_fixed)); + // Remove if rename failed. + @unlink($temporary_file); + if (file_exists($temporary_file)) { + $errors[16] = $t('unlinking @file failed.', array('@file' => $temporary_file)); + } + watchdog('advagg', 'Rename 2 failed. Current: %current Target: %target', array( + '%current' => $temporary_file, + '%target' => $temporary_file_fixed, + ), WATCHDOG_ERROR); + } + else { + $temporary_file = $temporary_file_fixed; + } } $temporary_file_copy = $temporary_file; @@ -931,18 +1163,27 @@ function advagg_save_data($uri, $data, $overwrite = FALSE) { $parts = pathinfo($parts['filename']); $extension = '.' . $parts['extension'] . $extension; } + // Move temp file into the dest dir, if not in there. // Add the extension on as well. - $temporary_file = str_replace(substr($temporary_file, 0, strpos($temporary_file, 'file_advagg_')), $dir, $temporary_file) . $extension; + $temporary_file_new = str_replace(substr($temporary_file, 0, strpos($temporary_file, 'advagg_file_')), $dir, $temporary_file) . $extension; - // Preform the rename, adding the extension to the temp file. - if (!@rename($temporary_file_copy, $temporary_file)) { + // Perform the rename, adding the extension to the temp file. + if (!@rename($temporary_file_copy, $temporary_file_new)) { + $errors[18] = $t('Renaming the filename (@incorrect) to (@correct) failed.', array('@incorrect' => $temporary_file_copy, '@correct' => $temporary_file_new)); // Remove if rename failed. @unlink($temporary_file_copy); + if (file_exists($temporary_file_copy)) { + $errors[16] = $t('unlinking @file failed.', array('@file' => $temporary_file_copy)); + } + watchdog('advagg', 'Rename 3 failed. Current: %current Target: %target', array( + '%current' => $temporary_file_copy, + '%target' => $temporary_file_new, + ), WATCHDOG_ERROR); } // Save to temporary filename in the destination directory. - $filepath = file_unmanaged_save_data($data, $temporary_file, FILE_EXISTS_REPLACE); + $filepath = file_unmanaged_save_data($data, $temporary_file_new, FILE_EXISTS_REPLACE); if ($filepath) { // Perform the rename operation. @@ -954,10 +1195,26 @@ function advagg_save_data($uri, $data, $overwrite = FALSE) { $result = @rename($filepath, $uri); // Remove temporary_file if rename failed. if (!$result) { + $errors[20] = $t('Renaming the filename (@incorrect) to (@correct) failed.', array('@incorrect' => $filepath, '@correct' => $uri)); + @unlink($filepath); + if (file_exists($filepath)) { + $errors[22] = $t('unlinking @file failed.', array('@file' => $filepath)); + } + watchdog('advagg', 'Rename 4 failed. Current: %current Target: %target', array( + '%current' => $filepath, + '%target' => $uri, + ), WATCHDOG_ERROR); } } } + else { + $errors[24] = $t('Write failed. @file', array('@file' => $temporary_file_new)); + watchdog('advagg', 'Write failed. Target: %target', array( + '%target' => $temporary_file_new, + ), WATCHDOG_ERROR); + } + return $errors; } // Helper functions. @@ -1037,20 +1294,22 @@ function advagg_get_atime($aggregate_filenames_hash, $aggregate_contents_hash, $ * * @param string $css * String of CSS. + * @param string $starting_string + * What to look for when starting to parse the string. * * @return array * array of css with only media queries. * * @see http://stackoverflow.com/questions/14145620/regular-expression-for-media-queries-in-css */ -function advagg_parse_media_blocks($css) { +function advagg_parse_media_blocks($css, $starting_string = '@media') { $media_blocks = array(); $start = 0; $last_start = 0; // Using the string as an array throughout this function. // http://php.net/types.string#language.types.string.substr - while (($start = strpos($css, "@media", $start)) !== FALSE) { + while (($start = strpos($css, $starting_string, $start)) !== FALSE) { // Stack to manage brackets. $s = array(); diff --git a/sites/all/modules/contrib/advagg/advagg.module b/sites/all/modules/contrib/advagg/advagg.module index 8974f94b0..9b8e3c85f 100644 --- a/sites/all/modules/contrib/advagg/advagg.module +++ b/sites/all/modules/contrib/advagg/advagg.module @@ -189,20 +189,45 @@ define('ADVAGG_AUTH_BASIC_PASS', ''); define('ADVAGG_SKIP_FAR_FUTURE_CHECK', FALSE); /** - * Skip far future check on status page. + * Skip preprocess check on status page. */ define('ADVAGG_SKIP_ENABLED_PREPROCESS_CHECK', FALSE); /** * Prefetch External domains for CSS/JS. */ -define('ADVAGG_BROWSER_DNS_PREFETCH', FALSE); +define('ADVAGG_BROWSER_DNS_PREFETCH', 0); /** * Function to use when converting a non scalar to a string. */ define('ADVAGG_SERIALIZE', 'json_encode'); +/** + * Default root dir for the advagg files; controls advagg_get_root_files_dir(). + */ +define('ADVAGG_ROOT_DIR_PREFIX', 'public://'); + +/** + * Skip gzip check on status page. + */ +define('ADVAGG_SKIP_GZIP_CHECK', FALSE); + +/** + * If true do not call file_create_url() for url() inside css files. + */ +define('ADVAGG_SKIP_FILE_CREATE_URL_INSIDE_CSS', FALSE); + +/** + * Display a message that the a CSS/JS file has changed. + */ +define('ADVAGG_SHOW_FILE_CHANGED_MESSAGE', TRUE); + +/** + * How long to wait until an aggregate with a missing file is written to disk. + */ +define('ADVAGG_FILE_READ_FAILURE_TIMEOUT', 3600); + // Core hook implementations. /** * Inbound URL rewrite helper. @@ -253,7 +278,7 @@ function advagg_url_inbound_alter(&$path, $original_path, $path_language) { $language_list = language_list(); $prefixes = array(); foreach ($language_list as $lang) { - if ($lang->enabled && strpos($request_path, $lang->prefix) !== FALSE) { + if ($lang->enabled && !empty($lang->prefix) && strpos($request_path, $lang->prefix) !== FALSE) { $prefixes[$lang->prefix] = $lang->prefix; } } @@ -290,6 +315,7 @@ function advagg_hook_info() { 'advagg_save_aggregate_pre_alter', 'advagg_save_aggregate_alter', 'advagg_build_aggregate_plans_alter', + 'advagg_build_aggregate_plans_post_alter', 'advagg_css_groups_alter', 'advagg_js_groups_alter', 'advagg_modify_css_pre_render_alter', @@ -299,6 +325,7 @@ function advagg_hook_info() { 'advagg_scan_for_changes', 'advagg_get_info_on_files_alter', 'advagg_context_alter', + 'advagg_missing_root_file', ); $hooks = array(); foreach ($advagg_hooks as $hook) { @@ -419,6 +446,13 @@ function advagg_module_implements_alter(&$implementations, $hook) { $implementations['advagg_js_compress'] = $item; } } + + // Move advagg to the bottom. + if ($hook === 'cron' && array_key_exists('advagg', $implementations)) { + $item = $implementations['advagg']; + unset($implementations['advagg']); + $implementations['advagg'] = $item; + } } /** @@ -456,6 +490,10 @@ function _advagg_locale_js_alter(&$js) { catch (PDOException $e) { // If it fails we don't care, javascript_parsed is either already written or // it will happen again on the next request. + // Still log it if in development mode. + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) { + watchdog('advagg', 'Development Mode - Caught PDO Exception: @info', array('@info' => $e)); + } } lock_release('locale_js_alter'); } @@ -520,7 +558,7 @@ function advagg_file_url_alter(&$original_uri) { // CDN module does not exist // CDN far future is disabled // CDN mode is not basic - // URI does not contain cdn/farfuture/ + // URI does not contain cdn/farfuture/. if ( variable_get('maintenance_mode', FALSE) || !module_exists('cdn') || !variable_get(CDN_BASIC_FARFUTURE_VARIABLE, CDN_BASIC_FARFUTURE_DEFAULT) @@ -546,6 +584,7 @@ function advagg_menu() { 'title' => "Generate CSS Aggregate", 'page callback' => 'advagg_missing_aggregate', 'type' => MENU_CALLBACK, + // Allow anyone to access these public css files. 'access callback' => TRUE, 'file path' => $file_path, 'file' => 'advagg.missing.inc', @@ -554,6 +593,7 @@ function advagg_menu() { 'title' => "Generate JS Aggregate", 'page callback' => 'advagg_missing_aggregate', 'type' => MENU_CALLBACK, + // Allow anyone to access these public js files. 'access callback' => TRUE, 'file path' => $file_path, 'file' => 'advagg.missing.inc', @@ -629,7 +669,7 @@ function advagg_cron($bypass_time_check = FALSE) { module_load_include('inc', 'advagg', 'advagg.cache'); $return[] = advagg_delete_stale_aggregates(); - // Remove orphaned files. + // Delete orphaned aggregates. $return[] = advagg_delete_orphaned_aggregates(); // Remove aggregates that include missing files. @@ -640,6 +680,13 @@ function advagg_cron($bypass_time_check = FALSE) { // Remove expired locks from the semaphore database table. $return[] = advagg_cleanup_semaphore_table(); + + // Remove old temp files. + $return[] = advagg_remove_temp_files(); + + // Refresh all locale files. + $return[] = advagg_refresh_all_locale_files(); + return $return; } @@ -849,7 +896,7 @@ function advagg_ajax_render_alter(&$commands) { $commands = array_merge($extra_commands, $commands); } if (!empty($settings)) { - array_unshift($commands, ajax_command_settings(drupal_array_merge_deep_array($settings['data']), TRUE)); + array_unshift($commands, ajax_command_settings(advagg_cleanup_settings_array(drupal_array_merge_deep_array($settings['data'])), TRUE)); } } @@ -1098,6 +1145,16 @@ function advagg_preprocess_html() { drupal_add_html_head($meta_ie_render_engine, 'meta_ie_render_engine'); } +/** + * Implements hook_form_FORM_ID_alter(). + * + * Give advice on how to temporarily disable css/js aggregation. + */ +function advagg_form_system_performance_settings_alter(&$form, &$form_state) { + module_load_include('admin.inc', 'advagg'); + advagg_admin_system_performance_settings_form($form, $form_state); +} + // Core CSS/JS override functions. /** * Callback for pre_render so elements can be modified before they are rendered. @@ -1116,20 +1173,22 @@ function advagg_preprocess_html() { * A render array that will render to a string of JavaScript tags. */ function advagg_modify_js_pre_render(array $elements) { - // Put children elements into a reference array. - $children = array(); - foreach ($elements as $key => &$value) { - if ($key !== '' && $key[0] === '#') { - continue; - } - $children[$key] = &$value; - } - unset($value); + // Get the children elements. + $children = array_intersect_key($elements, array_flip(element_children($elements))); // Allow other modules to modify $children and $elements before they are // rendered. // Call hook_advagg_modify_js_pre_render_alter() drupal_alter('advagg_modify_js_pre_render', $children, $elements); + + // Remove old children elements. + foreach ($children as $key => $value) { + if (isset($elements[$key])) { + unset($elements[$key]); + } + } + // Add in new children elements. + $elements += $children; return $elements; } @@ -1493,7 +1552,7 @@ function advagg_get_render_cache(array $full_css, array $js_scope_array) { // Get the cached data. $cached_data = cache_get_multiple($cids, 'cache_advagg_aggregates'); - // Set variables from the cache, + // Set variables from the cache. if (isset($cached_data[$css_cache_id])) { $css_cache = $cached_data[$css_cache_id]; } @@ -1600,23 +1659,29 @@ function _advagg_process_html(&$variables) { } // Render page_top and page_bottom into top level variables. - if (isset($variables['page']['page_top'])) { + if (isset($variables['page']) && is_array($variables['page']) && isset($variables['page']['page_top'])) { $variables['page_top'] = drupal_render($variables['page']['page_top']); } elseif (!isset($variables['page_top'])) { $variables['page_top'] = ''; } - if (isset($variables['page']['page_bottom'])) { + if (isset($variables['page']) && is_array($variables['page']) && isset($variables['page']['page_bottom'])) { $variables['page_bottom'] = drupal_render($variables['page']['page_bottom']); } elseif (!isset($variables['page_bottom'])) { $variables['page_bottom'] = ''; } // Place the rendered HTML for the page body into a top level variable. - if (isset($variables['page']['#children'])) { + if (isset($variables['page']) && is_array($variables['page']) && isset($variables['page']['#children'])) { $variables['page'] = $variables['page']['#children']; } + // Parts of drupal_get_html_head(). + $elements = drupal_add_html_head(); + if (is_callable('advagg_mod_html_head_alter')) { + advagg_mod_html_head_alter($elements); + } + // Get default javascript. // @see http://drupal.org/node/1279226 if (function_exists('drupal_add_js_page_defaults')) { @@ -1659,11 +1724,11 @@ function _advagg_process_html(&$variables) { } // Render the CSS. $variables['styles'] = drupal_render($full_css); - if (!empty($css_cache_id)) { + if (!empty($css_cache_id) && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 3) { // Save to the cache. cache_set($css_cache_id, array($variables['styles'], $full_css), 'cache_advagg_aggregates', CACHE_TEMPORARY); } - if (!empty($css_cache_id_no_alter)) { + if (!empty($css_cache_id_no_alter) && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 5) { // Save to the cache. cache_set($css_cache_id_no_alter, array($variables['styles'], $full_css), 'cache_advagg_aggregates', CACHE_TEMPORARY); } @@ -1741,13 +1806,19 @@ function _advagg_process_html(&$variables) { // Replace cached Drupal.settings with current Drupal.settings for this // page. - $merged = drupal_array_merge_deep_array($js_scope_settings_array_copy[$scope_settings]['settings']['data']); + $merged = advagg_cleanup_settings_array(drupal_array_merge_deep_array($js_scope_settings_array_copy[$scope_settings]['settings']['data'])); $json_data = advagg_json_encode($merged); if (!empty($json_data)) { // Record that this is being used. $js_settings_used[$scope_settings] = TRUE; + + // Find the end of the Drupal.settings. + $script_end = stripos($value, '', $start); + $settings_substring = substr($value, $start, $script_end - $start); + $json_end = strripos($settings_substring, '});'); + // Replace Drupal.settings json. - $value = substr($value, 0, $start + 30) . $json_data . substr($value, strripos($value, '});', $start) + 1); + $value = substr($value, 0, $start + 30) . $json_data . substr($value, $json_end + $start + 1); } } $add_to_variables[$scope] = $value; @@ -1834,10 +1905,10 @@ function _advagg_process_html(&$variables) { } unset($scripts_array); - if (!empty($js_cache_id) && !empty($js_cache)) { + if (!empty($js_cache_id) && !empty($js_cache) && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 3) { cache_set($js_cache_id, array($js_cache, $js_scope_array), 'cache_advagg_aggregates', CACHE_TEMPORARY); } - if (!empty($js_cache_id_no_alter) && !empty($js_cache)) { + if (!empty($js_cache_id_no_alter) && !empty($js_cache) && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 5) { cache_set($js_cache_id_no_alter, array($js_cache, $js_scope_array), 'cache_advagg_aggregates', CACHE_TEMPORARY); } } @@ -1872,7 +1943,19 @@ function _advagg_process_html(&$variables) { } } } - $variables['head'] = drupal_get_html_head(); + + // Add in the headers added by advagg for dns prefetch. + $head_elements = drupal_add_html_head(); + $advagg_elements = array(); + foreach ($head_elements as $key => $values) { + if (strpos($key, 'advagg_dns_prefetch') === 0) { + $advagg_elements[$key] = $values; + } + } + $elements += $advagg_elements; + // Parts of drupal_get_html_head(). + drupal_alter('html_head', $elements); + $variables['head'] = drupal_render($elements); // Output debug info. if (variable_get('advagg_debug', ADVAGG_DEBUG)) { @@ -1887,6 +1970,49 @@ function _advagg_process_html(&$variables) { } } +/** + * Shrink the ajaxPageState data. + * + * @param array $data + * Settings for javascript. + */ +function advagg_cleanup_settings_array(array $data) { + // Remove inline js from the ajaxPageState data. + if (isset($data['ajaxPageState']['js'])) { + foreach ((array) $data['ajaxPageState']['js'] as $key => $value) { + if (advagg_remove_short_keys($key)) { + if (is_array($data['ajaxPageState']['js']) && isset($data['ajaxPageState']['js'][$key])) { + unset($data['ajaxPageState']['js'][$key]); + } + elseif (is_object($data['ajaxPageState']['js']) && isset($data['ajaxPageState']['js']->{$key})) { + unset($data['ajaxPageState']['js']->{$key}); + } + } + } + } + // Remove inline css from the ajaxPageState data. + if (isset($data['ajaxPageState']['css'])) { + foreach ((array) $data['ajaxPageState']['css'] as $key => $value) { + if (advagg_remove_short_keys($key, 6)) { + if (is_object($data['ajaxPageState']['css']) && isset($data['ajaxPageState']['css']->{$key})) { + unset($data['ajaxPageState']['css']->{$key}); + } + elseif (is_array($data['ajaxPageState']['css']) && isset($data['ajaxPageState']['css'][$key])) { + unset($data['ajaxPageState']['css'][$key]); + } + } + } + } + // Remove settings from the js ajaxPageState data. + if (isset($data['ajaxPageState']['js']['settings'])) { + unset($data['ajaxPageState']['js']['settings']); + } + if (isset($data['ajaxPageState']['js']->settings)) { + unset($data['ajaxPageState']['js']->settings); + } + return $data; +} + /** * Find dns_prefetch and call advagg_add_dns_prefetch(). * @@ -1941,10 +2067,24 @@ function advagg_add_dns_prefetch($url) { 'rel' => 'dns-prefetch', 'href' => '//' . $parse['host'], ), - '#weight' => -999.9, + '#weight' => variable_get('advagg_browser_dns_prefetch', ADVAGG_BROWSER_DNS_PREFETCH) == 2 ? -1001 : -999.9, ); // Add markup for dns-prefetch to html_head. drupal_add_html_head($element, 'advagg_dns_prefetch:' . $parse['host']); + + // Build render array. Goes after charset tag. + if (!empty($parse['fragment']) && $parse['fragment'] === 'prefetch') { + $element = array( + '#type' => 'html_tag', + '#tag' => 'link', + '#attributes' => array( + 'rel' => 'prefetch', + 'href' => '//' . $parse['host'] . '/robots.txt', + ), + '#weight' => variable_get('advagg_browser_dns_prefetch', ADVAGG_BROWSER_DNS_PREFETCH) == 2 ? -1001 : -999.9, + ); + drupal_add_html_head($element, 'advagg_prefetch:' . $parse['host']); + } return TRUE; } @@ -2068,7 +2208,7 @@ function advagg_get_full_js(array $javascript = array(), $skip_alter = FALSE) { // Return an empty array if // no javascript is used, - // only the settings array is used and scope is header, + // only the settings array is used and scope is header. if ( empty($javascript) || (isset($javascript['settings']) && count($javascript) == 1) ) { @@ -2092,6 +2232,25 @@ function advagg_get_full_js(array $javascript = array(), $skip_alter = FALSE) { advagg_mod_js_move_to_footer($javascript); } } + + // If in development mode make sure the ajaxPageState css is there. + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) { + $have_css = FALSE; + foreach ($javascript['settings']['data'] as $setting) { + if (!empty($setting['ajaxPageState']['css'])) { + $have_css = TRUE; + break; + } + } + if (!$have_css) { + $css = drupal_add_css(); + if (!empty($css)) { + // Cast the array to an object to be on the safe side even if not empty. + $javascript['settings']['data'][]['ajaxPageState']['css'] = (object) array_fill_keys(array_keys($css), 1); + } + } + } + return $javascript; } @@ -2133,7 +2292,7 @@ function advagg_get_js($scope = 'header', array $javascript = array(), $ajax = F $javascript = advagg_get_full_js(); } - // Return an empty array if no javascript is used, + // Return an empty array if no javascript is used. if (empty($javascript)) { return array(); } @@ -2187,9 +2346,7 @@ function advagg_get_js($scope = 'header', array $javascript = array(), $ajax = F // Provide the page with information about the individual JavaScript files // used, information not otherwise available when aggregation is enabled. - // Also filter out empty items due to numeric array keys. - $setting['ajaxPageState']['js'] = array_fill_keys(array_filter(array_keys($items), 'advagg_remove_short_keys'), 1); - unset($setting['ajaxPageState']['js']['settings']); + $setting['ajaxPageState']['js'] = array_fill_keys(array_keys($items), 1); // If we're outputting the header scope, then this should be the final time // that drupal_get_js() is running, so add the setting to this output as well @@ -2200,7 +2357,31 @@ function advagg_get_js($scope = 'header', array $javascript = array(), $ajax = F // Also output the settings if we have pushed all javascript to the footer. if (isset($items['settings'])) { // Get settings from the static. - $javascript = &drupal_static('drupal_add_js', array()); + $javascript_static = &drupal_static('drupal_add_js', array()); + + // Merge in last js ajaxPageState into the current data. + $reverse_settings = array_reverse($javascript_static['settings']['data']); + foreach ($reverse_settings as $key => $values) { + if (is_scalar($values) || empty($values)) { + // Skip if not an array or object. + continue; + } + $values = (array) $values; + if (!array_key_exists('ajaxPageState', $values)) { + // Skip if $values['ajaxPageState'] does not exist. + continue; + } + if (is_scalar($values['ajaxPageState']) || empty($values['ajaxPageState'])) { + // Skip if not an array or object. + continue; + } + $values['ajaxPageState'] = (array) $values['ajaxPageState']; + if (array_key_exists('js', $values['ajaxPageState'])) { + // $values['ajaxPageState']['js'] exists. + $javascript['settings']['data'][] = $reverse_settings[$key]; + break; + } + } $items['settings']['data'] = $javascript['settings']['data']; // Add in this round of settings. @@ -2307,18 +2488,20 @@ function advagg_array_splice_assoc(array &$input, $offset, $length, $replacement /** * Callback for array_filter. Will return FALSE if strlen < 3. * - * @param string $array_value - * A value from an array. + * @param string $value + * A value from an array/object. + * @param int $min_len + * The strlen check length. * * @return bool * TRUE or FALSE. */ -function advagg_remove_short_keys($array_value) { - if (strlen($array_value) < 3) { - return FALSE; +function advagg_remove_short_keys($value, $min_len = 3) { + if (strlen($value) < $min_len) { + return TRUE; } else { - return TRUE; + return FALSE; } } @@ -2536,7 +2719,7 @@ function advagg_enabled() { $msg_set = TRUE; if (user_access('administer site configuration')) { drupal_set_message(t('The AdvAgg bypass cookie is currently enabled. Turn it off by going to the AdvAgg Operations page and clicking the Toggle the "aggregation bypass cookie" for this browser button.', array( - '@advagg_operations' => url(advagg_admin_config_root_path() . '/advagg/operations', array('fragment' => 'edit-bypass')), + '@advagg_operations' => url(advagg_admin_config_root_path() . '/advagg/operations', array('fragment' => 'edit-bypass')), ))); } else { @@ -2610,6 +2793,7 @@ function advagg_current_hooks_hash_array() { if (isset($aggregate_settings)) { return $aggregate_settings; } + list($css_path, $js_path) = advagg_get_root_files_dir(); // Put all enabled hooks and settings into a big array. $aggregate_settings = array( @@ -2625,6 +2809,8 @@ function advagg_current_hooks_hash_array() { 'advagg_convert_absolute_to_relative_path' => variable_get('advagg_convert_absolute_to_relative_path', ADVAGG_CONVERT_ABSOLUTE_TO_RELATIVE_PATH), 'advagg_convert_absolute_to_protocol_relative_path' => variable_get('advagg_convert_absolute_to_protocol_relative_path', ADVAGG_CONVERT_ABSOLUTE_TO_PROTOCOL_RELATIVE_PATH), 'advagg_force_https_path' => variable_get('advagg_force_https_path', ADVAGG_FORCE_HTTPS_PATH), + 'advagg_css_dir' => $css_path[0], + 'advagg_js_dir' => $js_path[0], ), 'hooks' => advagg_hooks_implemented(FALSE), ); @@ -2689,6 +2875,11 @@ function advagg_get_current_hooks_hash() { /** * Store settings associated with hash. * + * @param string $hash + * The hash. + * @param array $settings + * The settings associated with this hash. + * * @return MergeQuery * value from db_merge */ @@ -2729,6 +2920,7 @@ function advagg_hooks_implemented($all = TRUE) { if ($all) { $hooks += array( 'advagg_build_aggregate_plans_alter' => array(), + 'advagg_build_aggregate_plans_post_alter' => array(), 'advagg_changed_files' => array(), 'advagg_css_groups_alter' => array(), 'advagg_js_groups_alter' => array(), @@ -2738,6 +2930,7 @@ function advagg_hooks_implemented($all = TRUE) { 'advagg_hooks_implemented_alter' => array(), 'advagg_removed_aggregates' => array(), 'advagg_scan_for_changes' => array(), + 'advagg_missing_root_file' => array(), 'js_alter' => array(), 'css_alter' => array(), ); @@ -2821,8 +3014,10 @@ function advagg_get_root_files_dir($reset = FALSE) { // Make sure directories are available and writable. if (empty($css_paths) || empty($js_paths) || $reset) { - $css_paths[0] = 'public://advagg_css'; - $js_paths[0] = 'public://advagg_js'; + // Default is public://. + $prefix = variable_get('advagg_root_dir_prefix', ADVAGG_ROOT_DIR_PREFIX); + $css_paths[0] = $prefix . 'advagg_css'; + $js_paths[0] = $prefix . 'advagg_js'; file_prepare_directory($css_paths[0], FILE_CREATE_DIRECTORY); file_prepare_directory($js_paths[0], FILE_CREATE_DIRECTORY); @@ -3032,7 +3227,7 @@ function advagg_multi_update_atime(array $files) { $caches = cache_get_multiple($cids, 'cache_advagg_info'); if (!empty($caches)) { foreach ($caches as $cache) { - // See if the atime value needs to be updated; + // See if the atime value needs to be updated. if (!empty($cache->data['atime']) && $cache->data['atime'] > REQUEST_TIME - (12 * 60 * 60)) { // If atime is less than 12 hours old, do nothing. unset($records[$cache->cid]); @@ -3049,7 +3244,7 @@ function advagg_multi_update_atime(array $files) { ->key(array( 'aggregate_filenames_hash' => $record['aggregate_filenames_hash'], 'aggregate_contents_hash' => $record['aggregate_contents_hash'], - )) + )) ->fields(array('atime' => $record['atime'])) ->execute(); if (!$write_done && $result) { @@ -3318,7 +3513,7 @@ function advagg_pre_render_scripts(array $elements) { // Element properties that depend on item type. switch ($item['type']) { case 'setting': - $data = drupal_array_merge_deep_array($item['data']); + $data = advagg_cleanup_settings_array(drupal_array_merge_deep_array($item['data'])); $json_data = advagg_json_encode($data); $element['#value_prefix'] = $embed_prefix; $element['#value'] = 'jQuery.extend(Drupal.settings, ' . $json_data . ");"; @@ -3491,6 +3686,9 @@ function advagg_pre_render_styles(array $elements) { $element['#attributes']['href'] = advagg_file_create_url($group['data']); $element['#attributes']['media'] = $group['media']; $element['#browsers'] = $group['browsers']; + if (!empty($group['attributes'])) { + $element['#attributes'] += $group['attributes']; + } $elements[] = $element; } // The group can be aggregated, but hasn't been: combine multiple items @@ -3530,6 +3728,9 @@ function advagg_pre_render_styles(array $elements) { $element['#value'] = "\n" . implode("\n", $import_batch) . "\n"; $element['#attributes']['media'] = $group['media']; $element['#browsers'] = $group['browsers']; + if (!empty($group['attributes'])) { + $element['#attributes'] += $group['attributes']; + } $elements[] = $element; } } @@ -3552,6 +3753,9 @@ function advagg_pre_render_styles(array $elements) { $element['#attributes']['href'] = advagg_file_create_url($item['data']) . $query_string_separator . $query_string; $element['#attributes']['media'] = $item['media']; $element['#browsers'] = $group['browsers']; + if (!empty($group['attributes'])) { + $element['#attributes'] += $group['attributes']; + } $elements[] = $element; } } @@ -3568,6 +3772,9 @@ function advagg_pre_render_styles(array $elements) { $element['#value_suffix'] = $embed_suffix; $element['#attributes']['media'] = $group['media']; $element['#browsers'] = $group['browsers']; + if (!empty($group['attributes'])) { + $element['#attributes'] += $group['attributes']; + } $elements[] = $element; } else { @@ -3578,6 +3785,9 @@ function advagg_pre_render_styles(array $elements) { $element['#value_suffix'] = $embed_suffix; $element['#attributes']['media'] = $item['media']; $element['#browsers'] = $group['browsers']; + if (!empty($group['attributes'])) { + $element['#attributes'] += $group['attributes']; + } $elements[] = $element; } } @@ -3596,6 +3806,9 @@ function advagg_pre_render_styles(array $elements) { $element['#attributes']['href'] = $file_uri; $element['#attributes']['media'] = $item['media']; $element['#browsers'] = $group['browsers']; + if (!empty($group['attributes'])) { + $element['#attributes'] += $group['attributes']; + } $elements[] = $element; } break; @@ -3714,9 +3927,9 @@ function advagg_group_js(array $javascript) { $group_keys = NULL; // Log the error as well. watchdog('advagg', 'Bad javascript was added. Type is unknown. @key - @item', array( - '@key' => $key, - '@item' => print_r($item, TRUE), - ), WATCHDOG_NOTICE); + '@key' => $key, + '@item' => print_r($item, TRUE), + ), WATCHDOG_NOTICE); break; } @@ -3824,7 +4037,7 @@ function advagg_json_encode($data) { $php530 = version_compare(PHP_VERSION, '5.3.0', '>='); } - // Use fallback drupal encoder if PHP < 5.3.0 + // Use fallback drupal encoder if PHP < 5.3.0. if (!$php530) { return @drupal_json_encode($data); } @@ -3931,13 +4144,15 @@ function advagg_scan_filesystem_for_changes_live() { // Do not report on css files manged in the parts directory. continue; } - $ext = pathinfo($filename, PATHINFO_EXTENSION); - drupal_set_message(t('The file %filename has changed. %db_usage aggregates are using this file. %db_count db cache entries and all %type full cache entries have been flushed from the cache bins.', array( - '%filename' => $filename, - '%db_usage' => count($data[0]), - '%db_count' => count($data[1]), - '%type' => $ext, - ))); + if (variable_get('advagg_show_file_changed_message', ADVAGG_SHOW_FILE_CHANGED_MESSAGE)) { + $ext = pathinfo($filename, PATHINFO_EXTENSION); + drupal_set_message(t('The file %filename has changed. %db_usage aggregates are using this file. %db_count db cache entries and all %type full cache entries have been flushed from the cache bins.', array( + '%filename' => $filename, + '%db_usage' => count($data[0]), + '%db_count' => count($data[1]), + '%type' => $ext, + ))); + } } } @@ -3985,10 +4200,7 @@ function advagg_convert_abs_to_rel($path) { * The path. */ function advagg_convert_abs_to_protocol($path) { - if (strpos($path, 'https://') === 0) { - $path = substr($path, 6); - } - elseif (strpos($path, 'http://') === 0) { + if (strpos($path, 'http://') === 0) { $path = substr($path, 5); } return $path; @@ -4090,7 +4302,7 @@ function advagg_file_create_url($path, array $aggregate_settings = array()) { * * @see drupal_load_stylesheet() */ -function advagg_load_stylesheet($file, $optimize = FALSE, $reset_basepath = TRUE) { +function advagg_load_stylesheet($file, $optimize = FALSE, $reset_basepath = TRUE, $contents = '') { // These statics are not cache variables, so we don't use drupal_static(). static $_optimize, $basepath; if ($reset_basepath) { @@ -4116,7 +4328,9 @@ function advagg_load_stylesheet($file, $optimize = FALSE, $reset_basepath = TRUE // stylesheets in their .info file that don't exist in the theme's path, // but are merely there to disable certain module CSS files. $content = ''; - $contents = @file_get_contents($file); + if (empty($contents) && !empty($file)) { + $contents = @file_get_contents($file); + } if ($contents) { // Return the processed stylesheet. $content = advagg_load_stylesheet_content($contents, $_optimize); @@ -4130,6 +4344,67 @@ function advagg_load_stylesheet($file, $optimize = FALSE, $reset_basepath = TRUE return $content; } +/** + * Decodes UTF byte-order mark (BOM) into the encoding's name. + * + * @param string $data + * The data possibly containing a BOM. This can be the entire contents of + * a file, or just a fragment containing at least the first five bytes. + * + * @return string|bool + * The name of the encoding, or FALSE if no byte order mark was present. + * + * @see https://api.drupal.org/api/drupal/core!lib!Drupal!Component!Utility!Unicode.php/function/Unicode%3A%3AencodingFromBOM/8 + */ +function advagg_get_encoding_from_bom($data) { + static $bom_map = array( + "\xEF\xBB\xBF" => 'UTF-8', + "\xFE\xFF" => 'UTF-16BE', + "\xFF\xFE" => 'UTF-16LE', + "\x00\x00\xFE\xFF" => 'UTF-32BE', + "\xFF\xFE\x00\x00" => 'UTF-32LE', + "\x2B\x2F\x76\x38" => 'UTF-7', + "\x2B\x2F\x76\x39" => 'UTF-7', + "\x2B\x2F\x76\x2B" => 'UTF-7', + "\x2B\x2F\x76\x2F" => 'UTF-7', + "\x2B\x2F\x76\x38\x2D" => 'UTF-7', + ); + + foreach ($bom_map as $bom => $encoding) { + if (strpos($data, $bom) === 0) { + return $encoding; + } + } + return FALSE; +} + +/** + * Converts data to UTF-8. + * + * Requires the iconv, GNU recode or mbstring PHP extension. + * + * @param string $data + * The data to be converted. + * @param string $encoding + * The encoding that the data is in. + * + * @return string|bool + * Converted data or FALSE. + */ +function advagg_convert_to_utf8($data, $encoding) { + if (function_exists('iconv')) { + return @iconv($encoding, 'utf-8', $data); + } + elseif (function_exists('mb_convert_encoding')) { + return @mb_convert_encoding($data, 'utf-8', $encoding); + } + elseif (function_exists('recode_string')) { + return @recode_string($encoding . '..utf-8', $data); + } + // Cannot convert. + return FALSE; +} + /** * Processes the contents of a stylesheet for aggregation. * @@ -4145,6 +4420,12 @@ function advagg_load_stylesheet($file, $optimize = FALSE, $reset_basepath = TRUE * @see drupal_load_stylesheet_content() */ function advagg_load_stylesheet_content($contents, $optimize = FALSE) { + // If a BOM is found, convert the file to UTF-8. + $encoding = advagg_get_encoding_from_bom($contents); + if (!empty($encoding)) { + $contents = advagg_convert_to_utf8($contents, $encoding); + } + if ($optimize) { // Perform some safe CSS optimizations. // Regexp to match comment blocks. @@ -4241,12 +4522,27 @@ function advagg_aggressive_cache_conflicts() { $hooks[$hook] = module_implements($hook); // Also check themes as drupal_alter() allows for themes to alter things. - $theme_keys = array_keys(list_themes()); + $themes = list_themes(); + $theme_keys = array_keys($themes); if (!empty($theme_keys)) { foreach ($theme_keys as $theme_key) { $function = $theme_key . '_' . $hook; + // Search loaded themes. if (function_exists($function)) { $hooks[$hook][] = $theme_key; + continue; + } + // Skip disabled themes. + if (empty($themes[$theme_key]->status)) { + continue; + } + // Search enabled but not loaded themes. + $file = dirname($themes[$theme_key]->filename) . '/template.php'; + if (file_exists($file)) { + $contents = file_get_contents($file); + if (stripos($contents, $function)) { + $hooks[$hook][] = $theme_key; + } } } } @@ -4267,6 +4563,9 @@ function advagg_aggressive_cache_conflicts() { // Popular contrib. // + // No control; same every time. + 'at_commerce', + // ais_adaptive_styles variable; Default: array(). // ais_adaptive_styles_method; Default: 'both-max'. // 'ais', @@ -4277,7 +4576,7 @@ function advagg_aggressive_cache_conflicts() { // drupal_static('clientside_validation_settings') array. // 'clientside_validation', // - // version_compare(VERSION, '7.14', '<') + // version_compare(VERSION, '7.14', '<'). 'conditional_fields', // _css_injector_load_rule() function. @@ -4350,7 +4649,7 @@ function advagg_aggressive_cache_conflicts() { * @param array $parsed * Array from parse_url(). * @param bool $strip_query_and_fragment - * If set to TRUE the query and fragemnt will be removed from the output. + * If set to TRUE the query and fragment will be removed from the output. * * @return string * URI is returned. @@ -4358,7 +4657,24 @@ function advagg_aggressive_cache_conflicts() { * @see http://php.net/parse-url#85963 */ function advagg_glue_url(array $parsed, $strip_query_and_fragment = FALSE) { - $uri = isset($parsed['scheme']) ? $parsed['scheme'] . ':' . ((strtolower($parsed['scheme']) === 'mailto') ? '' : '//') : ''; + $uri = ''; + if (isset($parsed['scheme'])) { + switch (strtolower($parsed['scheme'])) { + // Mailto uri. + case 'mailto': + $uri .= $parsed['scheme'] . ':'; + break; + + // Protocol relative uri. + case '//': + $uri .= $parsed['scheme']; + break; + + // Standard uri. + default: + $uri .= $parsed['scheme'] . '://'; + } + } $uri .= isset($parsed['user']) ? $parsed['user'] . (isset($parsed['pass']) ? ':' . $parsed['pass'] : '') . '@' : ''; $uri .= isset($parsed['host']) ? $parsed['host'] : ''; $uri .= !empty($parsed['port']) ? ':' . $parsed['port'] : ''; @@ -4373,3 +4689,15 @@ function advagg_glue_url(array $parsed, $strip_query_and_fragment = FALSE) { } return $uri; } + +/** + * Clear certain caches on form submit. + */ +function advagg_cache_clear_admin_submit() { + $cache_bins = advagg_flush_caches(); + foreach ($cache_bins as $bin) { + cache_clear_all('*', $bin, TRUE); + } + cache_clear_all('hook_info', 'cache_bootstrap'); + cache_clear_all('advagg_hooks_implemented:', 'cache_bootstrap', TRUE); +} diff --git a/sites/all/modules/contrib/advagg/advagg_bundler/advagg_bundler.admin.inc b/sites/all/modules/contrib/advagg/advagg_bundler/advagg_bundler.admin.inc index 5c54d30b0..e37ff25ae 100644 --- a/sites/all/modules/contrib/advagg/advagg_bundler/advagg_bundler.admin.inc +++ b/sites/all/modules/contrib/advagg/advagg_bundler/advagg_bundler.admin.inc @@ -66,6 +66,22 @@ function advagg_bundler_admin_settings_form() { ), ); + $form['advagg_bundler_grouping_logic'] = array( + '#type' => 'radios', + '#title' => t('Grouping logic'), + '#default_value' => variable_get('advagg_bundler_grouping_logic', ADVAGG_BUNDLER_GROUPING_LOGIC), + '#options' => array( + 0 => t('File count'), + 1 => t('File size'), + ), + '#description' => t('If file count is selected then each bundle will try to have a similar number of original files aggregated inside of it. If file size is selected then each bundle will try to have a similar file size.'), + '#states' => array( + 'disabled' => array( + '#edit-advagg-bundler-active' => array('checked' => FALSE), + ), + ), + ); + $form['info'] = array( '#type' => 'fieldset', '#collapsible' => TRUE, @@ -96,10 +112,8 @@ function advagg_bundler_admin_settings_form() { */ function advagg_bundler_admin_settings_form_submit($form, &$form_state) { // Clear caches. - $cache_bins = advagg_flush_caches(); - foreach ($cache_bins as $bin) { - cache_clear_all('*', $bin, TRUE); - } + advagg_cache_clear_admin_submit(); + // Unset advagg_bundler_info. if (isset($form_state['values']['advagg_bundler_info'])) { unset($form_state['values']['advagg_bundler_info']); diff --git a/sites/all/modules/contrib/advagg/advagg_bundler/advagg_bundler.advagg.inc b/sites/all/modules/contrib/advagg/advagg_bundler/advagg_bundler.advagg.inc index e712cc17f..4d5ab1a9a 100644 --- a/sites/all/modules/contrib/advagg/advagg_bundler/advagg_bundler.advagg.inc +++ b/sites/all/modules/contrib/advagg/advagg_bundler/advagg_bundler.advagg.inc @@ -112,10 +112,18 @@ function advagg_bundler_merge(array &$groupings, $max) { $counts = array(); $group_hash_counts = array(); foreach ($groupings as $key => $values) { - $counts[$key] = count($values); // Get the group hash counts. + $file_size = 0; + $group_hash_counts[$key] = 0; foreach ($values as $data) { - $group_hash_counts[$key] = intval(substr($data['bundler']['group_hash'], 0, 8)); + $file_size += empty($data['bundler']['filesize_processed']) ? $data['bundler']['filesize'] : $data['bundler']['filesize_processed']; + $group_hash_counts[$key] += intval(substr($data['bundler']['group_hash'], 0, 8)); + } + if (variable_get('advagg_bundler_grouping_logic', ADVAGG_BUNDLER_GROUPING_LOGIC) == 0) { + $counts[$key] = count($values); + } + elseif (variable_get('advagg_bundler_grouping_logic', ADVAGG_BUNDLER_GROUPING_LOGIC) == 1) { + $counts[$key] = $file_size; } } @@ -167,7 +175,7 @@ function advagg_bundler_merge(array &$groupings, $max) { // Get best merge candidate. // We are looking for the smallest file count. $min is populated with a // large number (15 bits) so it won't be selected in this process. - $min = 32767; + $min = PHP_INT_MAX; $first = NULL; $last = NULL; $last_min = NULL; @@ -234,7 +242,7 @@ function advagg_bundler_merge(array &$groupings, $max) { // Add the bad groups to the smallest grouping in this set. if (!empty($bad_groups)) { $merge_candidate_key = ''; - $merge_candidate_count = 32767; + $merge_candidate_count = PHP_INT_MAX; $bad_group = array(); foreach ($groupings as $key => $group) { if (isset($bad_groups[$key])) { diff --git a/sites/all/modules/contrib/advagg/advagg_bundler/advagg_bundler.info b/sites/all/modules/contrib/advagg/advagg_bundler/advagg_bundler.info index 5e5e0cb0a..bd9bea914 100644 --- a/sites/all/modules/contrib/advagg/advagg_bundler/advagg_bundler.info +++ b/sites/all/modules/contrib/advagg/advagg_bundler/advagg_bundler.info @@ -6,9 +6,9 @@ dependencies[] = advagg configure = admin/config/development/performance/advagg/bundler -; Information added by Drupal.org packaging script on 2015-07-18 -version = "7.x-2.14" +; Information added by Drupal.org packaging script on 2016-03-29 +version = "7.x-2.18" core = "7.x" project = "advagg" -datestamp = "1437181444" +datestamp = "1459291143" diff --git a/sites/all/modules/contrib/advagg/advagg_bundler/advagg_bundler.module b/sites/all/modules/contrib/advagg/advagg_bundler/advagg_bundler.module index c63333779..ae7fbbf20 100644 --- a/sites/all/modules/contrib/advagg/advagg_bundler/advagg_bundler.module +++ b/sites/all/modules/contrib/advagg/advagg_bundler/advagg_bundler.module @@ -30,6 +30,11 @@ define('ADVAGG_BUNDLER_OUTDATED', 1209600); */ define('ADVAGG_BUNDLER_ACTIVE', TRUE); +/** + * Default value to for bundler, set to file count. + */ +define('ADVAGG_BUNDLER_GROUPING_LOGIC', 0); + /** * Implements hook_menu(). */ @@ -97,11 +102,15 @@ function advagg_bundler_enabled() { * Filename. * @param bool $force * Bypass the cache and get a fresh version of the analysis. + * @param bool $safesql + * Turn off SQL language that might cause errors. + * @param int $depth + * Used to prevent endless loops. * * @return string * String to be used for the grouping key. */ -function advagg_bundler_analysis($filename = '', $force = FALSE) { +function advagg_bundler_analysis($filename = '', $force = FALSE, $safesql = FALSE, $depth = 0) { // Cache query in a static. static $analysis = array(); if (empty($analysis)) { @@ -152,160 +161,18 @@ function advagg_bundler_analysis($filename = '', $force = FALSE) { } if ($force || empty($cache->data)) { - // "Magic Query"; only needs to run once. Results are cached. - // - // This is what the raw SQL looks like: - // - // SELECT - // af.filename AS filename, - // af.filesize AS filesize, - // af.mtime AS mtime, - // af.changes AS changes, - // af.linecount AS linecount, - // af.filename_hash AS filename_hash, - // aa.counter AS counter, - // aa.hashlist AS hashlist - // FROM advagg_files af - // INNER JOIN ( - // SELECT - // aa.filename_hash AS filename_hash, - // LPAD(CAST(COUNT(aav.aggregate_filenames_hash) AS char(8)), 8, '0') AS counter, - // HEX(SHA1(GROUP_CONCAT(DISTINCT aa.aggregate_filenames_hash ORDER BY aa.aggregate_filenames_hash ASC))) AS hashlist - // FROM advagg_aggregates aa - // INNER JOIN advagg_aggregates_versions aav - // ON aav.aggregate_filenames_hash = aa.aggregate_filenames_hash - // AND aav.root = 1 - // AND aav.atime > (UNIX_TIMESTAMP() - 1209600) - // GROUP BY aa.filename_hash - // ) aa ON af.filename_hash = aa.filename_hash - // - // Return a count of how many root bundles all files are used in. Count is - // padded with eight zeros so the count can be key sorted as a string - // without worrying about it getting put in the wrong order. - // Return the bundle_md5's value; we need something more unique than count - // when grouping together. - // Return the filename. Used for lookup. - // We join the advagg bundles and files together making sure to only use - // root bundles that have been used in the last 2 weeks. This prevents an - // old site structure from influencing new bundles. - // Grouping by the filename gives us the count and makes it so we don't - // return a lot of rows; - $db_type = Database::getConnection()->databaseType(); - $schema = Database::getConnection()->schema(); - $mssql_group_concat = method_exists($schema, 'functionExists') && $schema->functionExists('GROUP_CONCAT'); - $mssql_lpad = method_exists($schema, 'functionExists') && $schema->functionExists('LPAD'); - $mssql_md5 = method_exists($schema, 'functionExists') && $schema->functionExists('MD5'); - - // Create join query for the advagg_aggregates table. - $subquery_aggregates = db_select('advagg_aggregates', 'aa'); - - // Counter column. - $fields = array('counter'); - if ($db_type === 'sqlsrv' && !$mssql_lpad) { - // MS SQL does not support LPAD. - $subquery_aggregates->addExpression("RIGHT(REPLICATE('0',8) + CAST(COUNT(aav.aggregate_filenames_hash) AS char(8)),8)", 'counter'); - } - elseif ($db_type === 'sqlite') { - // SQLite does not support LPAD. - $subquery_aggregates->addExpression("substr('00000000' || CAST(COUNT(aav.aggregate_filenames_hash) AS char(8)), -8, 8)", 'counter'); - } - else { - $subquery_aggregates->addExpression("LPAD(CAST(COUNT(aav.aggregate_filenames_hash) AS char(8)), 8, '0')", 'counter'); - } - - // Hashlist column. - if ($db_type === 'mysql') { - $fields[] = 'hashlist'; - db_query('SET SESSION group_concat_max_len = 65535'); - $subquery_aggregates->addExpression('HEX(SHA1(GROUP_CONCAT(DISTINCT aa.aggregate_filenames_hash ORDER BY aa.aggregate_filenames_hash ASC)))', 'hashlist'); - } - elseif ($db_type === 'pgsql') { - $database_connection = Database::getConnection(); - if (version_compare($database_connection->version(), '9') >= 0) { - $fields[] = 'hashlist'; - $subquery_aggregates->addExpression("MD5(STRING_AGG(DISTINCT(aa.aggregate_filenames_hash), ',' ORDER BY aa.aggregate_filenames_hash ASC))", 'hashlist'); - } - } - elseif ($db_type === 'sqlite') { - $fields[] = 'hashlist'; - $subquery_aggregates->addExpression('GROUP_CONCAT(DISTINCT aa.aggregate_filenames_hash)', 'hashlist'); - $subquery_aggregates->orderBy("aa.aggregate_filenames_hash", "ASC"); - } - elseif ($db_type === 'sqlsrv' && !$mssql_group_concat) { - $fields[] = 'hashlist'; - if ($mssql_md5) { - $subquery_aggregates->addExpression('MD5(GROUP_CONCAT(DISTINCT aa.aggregate_filenames_hash))', 'hashlist'); - } - else { - $subquery_aggregates->addExpression('GROUP_CONCAT(DISTINCT aa.aggregate_filenames_hash)', 'hashlist'); - } - $subquery_aggregates->orderBy("aa.aggregate_filenames_hash", "ASC"); + try { + $analysis = advagg_bundler_analyisis_query($safesql); + // Save results to the cache. + cache_set($ideal_cid, $analysis, 'cache_advagg_aggregates', CACHE_TEMPORARY); } - - // Create join for the advagg_aggregates_versions table. - // 1209600 = 2 weeks. - $time = REQUEST_TIME - max(172800, variable_get('advagg_bundler_outdated', ADVAGG_BUNDLER_OUTDATED), '>'); - $subquery_aggregates->join('advagg_aggregates_versions', 'aav', "aav.aggregate_filenames_hash=aa.aggregate_filenames_hash AND aav.root=1 AND aav.atime > $time"); - - $subquery_aggregates = $subquery_aggregates->fields('aa', array('filename_hash')) - ->groupBy('aa.filename_hash'); - - // Create main query for the advagg_files table. - $query = db_select('advagg_files', 'af'); - $query->join($subquery_aggregates, 'aa', 'af.filename_hash=aa.filename_hash'); - $query = $query->fields('af', array( - 'filename', - 'filesize', - 'mtime', - 'changes', - 'linecount', - 'filename_hash', - )) - ->fields('aa', $fields); - $query->comment('Query called from ' . __FUNCTION__ . '()'); - $results = $query->execute(); - - $analysis = array(); - foreach ($results as $row) { - // Implement slower GROUP_CONCAT functionality for non mysql databases. - if (empty($row->hashlist)) { - $subquery_aggregates_versions = db_select('advagg_aggregates_versions', 'aav') - ->fields('aav') - ->condition('aav.root', 1) - ->condition('aav.atime', REQUEST_TIME - max(172800, variable_get('advagg_bundler_outdated', ADVAGG_BUNDLER_OUTDATED), '>'), '>'); - - $subquery_aggregates = db_select('advagg_aggregates', 'aa'); - $subquery_aggregates->join($subquery_aggregates_versions, 'aav', 'aav.aggregate_filenames_hash=aa.aggregate_filenames_hash'); - $subquery_aggregates = $subquery_aggregates->fields('aa', array('aggregate_filenames_hash')) - ->condition('aa.filename_hash', $row->filename_hash) - ->groupBy('aa.aggregate_filenames_hash') - ->orderBy('aa.aggregate_filenames_hash', 'ASC'); - $subquery_aggregates->comment('Query called from ' . __FUNCTION__ . '()'); - $aa_results = $subquery_aggregates->execute(); - $aa_rows = array(); - foreach ($aa_results as $aa_row) { - $aa_rows[] = $aa_row->aggregate_filenames_hash; - } - $row->hashlist = implode(',', $aa_rows); + catch (PDOException $e) { + if ($depth > 2) { + throw $e; } - - $row->hashlist = drupal_hash_base64($row->hashlist); - $analysis[$row->filename] = array( - 'group_hash' => $row->counter . ' ' . $row->hashlist, - 'mtime' => $row->mtime, - 'filesize' => $row->filesize, - 'linecount' => $row->linecount, - 'changes' => $row->changes, - ); + $depth++; + return advagg_bundler_analysis($filename, TRUE, TRUE, $depth); } - arsort($analysis); - - // Invoke hook_advagg_bundler_analysis_alter() to give installed modules a - // chance to alter the analysis array. - drupal_alter('advagg_bundler_analysis', $analysis); - - // Save results to the cache. - cache_set($ideal_cid, $analysis, 'cache_advagg_aggregates', CACHE_TEMPORARY); } else { $analysis = $cache->data; @@ -326,3 +193,193 @@ function advagg_bundler_analysis($filename = '', $force = FALSE) { // didn't give us anything. return 0; } + +/** + * Run the analysis query and return the analysis array. + * + * @param bool $safesql + * Turn off SQL language that might cause errors. + * + * @return array + * The analysis array. + */ +function advagg_bundler_analyisis_query($safesql) { + // "Magic Query"; only needs to run once. Results are cached. + // + // This is what the raw SQL looks like: + // + // SELECT + // af.filename AS filename, + // af.filesize AS filesize, + // af.mtime AS mtime, + // af.changes AS changes, + // af.linecount AS linecount, + // af.filename_hash AS filename_hash, + // aa.counter AS counter, + // aa.hashlist AS hashlist + // FROM advagg_files af + // INNER JOIN ( + // SELECT + // aa.filename_hash AS filename_hash, + // LPAD(CAST(COUNT(aav.aggregate_filenames_hash) AS char(8)), 8, '0') AS counter, + // HEX(SHA1(GROUP_CONCAT(DISTINCT aa.aggregate_filenames_hash ORDER BY aa.aggregate_filenames_hash ASC))) AS hashlist + // FROM advagg_aggregates aa + // INNER JOIN advagg_aggregates_versions aav + // ON aav.aggregate_filenames_hash = aa.aggregate_filenames_hash + // AND aav.root = 1 + // AND aav.atime > (UNIX_TIMESTAMP() - 1209600) + // GROUP BY aa.filename_hash + // ) aa ON af.filename_hash = aa.filename_hash + // + // Return a count of how many root bundles all files are used in. Count is + // padded with eight zeros so the count can be key sorted as a string + // without worrying about it getting put in the wrong order. + // Return the bundle_md5's value; we need something more unique than count + // when grouping together. + // Return the filename. Used for lookup. + // We join the advagg bundles and files together making sure to only use + // root bundles that have been used in the last 2 weeks. This prevents an + // old site structure from influencing new bundles. + // Grouping by the filename gives us the count and makes it so we don't + // return a lot of rows. + $db_type = Database::getConnection()->databaseType(); + $schema = Database::getConnection()->schema(); + if ($safesql) { + $mssql_group_concat = FALSE; + $mssql_lpad = FALSE; + $mssql_md5 = FALSE; + $pg9 = FALSE; + } + else { + $mssql_group_concat = method_exists($schema, 'functionExists') && $schema->functionExists('GROUP_CONCAT'); + $mssql_lpad = method_exists($schema, 'functionExists') && $schema->functionExists('LPAD'); + $mssql_md5 = method_exists($schema, 'functionExists') && $schema->functionExists('MD5'); + if ($db_type === 'pgsql') { + $database_connection = Database::getConnection(); + $pg9 = FALSE; + if (version_compare($database_connection->version(), '9') >= 0) { + $pg9 = TRUE; + } + } + } + + // Create join query for the advagg_aggregates table. + $subquery_aggregates = db_select('advagg_aggregates', 'aa'); + + // Counter column. + $fields = array('counter'); + if ($db_type === 'sqlsrv' && !$mssql_lpad) { + // MS SQL does not support LPAD. + $subquery_aggregates->addExpression("RIGHT(REPLICATE('0',8) + CAST(COUNT(aav.aggregate_filenames_hash) AS char(8)),8)", 'counter'); + } + elseif ($db_type === 'sqlite') { + // SQLite does not support LPAD. + $subquery_aggregates->addExpression("substr('00000000' || CAST(COUNT(aav.aggregate_filenames_hash) AS char(8)), -8, 8)", 'counter'); + } + else { + $subquery_aggregates->addExpression("LPAD(CAST(COUNT(aav.aggregate_filenames_hash) AS char(8)), 8, '0')", 'counter'); + } + + // Hashlist column. + if ($db_type === 'mysql') { + $fields[] = 'hashlist'; + db_query('SET SESSION group_concat_max_len = 65535'); + $subquery_aggregates->addExpression('HEX(SHA1(GROUP_CONCAT(DISTINCT aa.aggregate_filenames_hash ORDER BY aa.aggregate_filenames_hash ASC)))', 'hashlist'); + } + elseif ($db_type === 'pgsql') { + if ($pg9) { + $fields[] = 'hashlist'; + $subquery_aggregates->addExpression("MD5(STRING_AGG(DISTINCT(aa.aggregate_filenames_hash), ',' ORDER BY aa.aggregate_filenames_hash ASC))", 'hashlist'); + } + } + elseif ($db_type === 'sqlite') { + $fields[] = 'hashlist'; + $subquery_aggregates->addExpression('GROUP_CONCAT(DISTINCT aa.aggregate_filenames_hash)', 'hashlist'); + $subquery_aggregates->orderBy("aa.aggregate_filenames_hash", "ASC"); + } + elseif ($db_type === 'sqlsrv' && $mssql_group_concat) { + $fields[] = 'hashlist'; + if ($mssql_md5) { + $subquery_aggregates->addExpression('MD5(GROUP_CONCAT(DISTINCT aa.aggregate_filenames_hash))', 'hashlist'); + } + else { + $subquery_aggregates->addExpression('GROUP_CONCAT(DISTINCT aa.aggregate_filenames_hash)', 'hashlist'); + } + // The ORDER BY clause is invalid in views, inline functions, + // derived tables, subqueries, and common table expressions, unless TOP or + // FOR XML is also specified. So no point in doing an order-by like in the + // other cases. + } + + // Create join for the advagg_aggregates_versions table. + // 1209600 = 2 weeks. + $time = REQUEST_TIME - max(172800, variable_get('advagg_bundler_outdated', ADVAGG_BUNDLER_OUTDATED), '>'); + $subquery_aggregates->join('advagg_aggregates_versions', 'aav', "aav.aggregate_filenames_hash=aa.aggregate_filenames_hash AND aav.root=1 AND aav.atime > $time"); + + $subquery_aggregates = $subquery_aggregates->fields('aa', array('filename_hash')) + ->groupBy('aa.filename_hash'); + + // Create main query for the advagg_files table. + $af_fields = array( + 'filename', + 'filesize', + 'mtime', + 'changes', + 'linecount', + 'filename_hash', + ); + // Make drupal_get_installed_schema_version() available. + include_once DRUPAL_ROOT . '/includes/install.inc'; + if (drupal_get_installed_schema_version('advagg') >= 7211) { + $af_fields[] = 'filesize_processed'; + } + + $query = db_select('advagg_files', 'af'); + $query->join($subquery_aggregates, 'aa', 'af.filename_hash=aa.filename_hash'); + $query = $query->fields('af', $af_fields) + ->fields('aa', $fields); + $query->comment('Query called from ' . __FUNCTION__ . '()'); + $results = $query->execute(); + + $analysis = array(); + foreach ($results as $row) { + // Implement slower GROUP_CONCAT functionality for non mysql databases. + if (empty($row->hashlist)) { + $subquery_aggregates_versions = db_select('advagg_aggregates_versions', 'aav') + ->fields('aav') + ->condition('aav.root', 1) + ->condition('aav.atime', REQUEST_TIME - max(172800, variable_get('advagg_bundler_outdated', ADVAGG_BUNDLER_OUTDATED), '>'), '>'); + + $subquery_aggregates = db_select('advagg_aggregates', 'aa'); + $subquery_aggregates->join($subquery_aggregates_versions, 'aav', 'aav.aggregate_filenames_hash=aa.aggregate_filenames_hash'); + $subquery_aggregates = $subquery_aggregates->fields('aa', array('aggregate_filenames_hash')) + ->condition('aa.filename_hash', $row->filename_hash) + ->groupBy('aa.aggregate_filenames_hash') + ->orderBy('aa.aggregate_filenames_hash', 'ASC'); + $subquery_aggregates->comment('Query called from ' . __FUNCTION__ . '()'); + $aa_results = $subquery_aggregates->execute(); + $aa_rows = array(); + foreach ($aa_results as $aa_row) { + $aa_rows[] = $aa_row->aggregate_filenames_hash; + } + $row->hashlist = implode(',', $aa_rows); + } + + $row->hashlist = drupal_hash_base64($row->hashlist); + $analysis[$row->filename] = array( + 'group_hash' => $row->counter . ' ' . $row->hashlist, + 'mtime' => $row->mtime, + 'filesize' => $row->filesize, + 'filesize_processed' => empty($row->filesize_processed) ? $row->filesize : $row->filesize_processed, + 'linecount' => $row->linecount, + 'changes' => $row->changes, + ); + } + arsort($analysis); + + // Invoke hook_advagg_bundler_analysis_alter() to give installed modules a + // chance to alter the analysis array. + drupal_alter('advagg_bundler_analysis', $analysis); + + return $analysis; +} diff --git a/sites/all/modules/contrib/advagg/advagg_css_cdn/advagg_css_cdn.info b/sites/all/modules/contrib/advagg/advagg_css_cdn/advagg_css_cdn.info index 825951b0d..ace9fb376 100644 --- a/sites/all/modules/contrib/advagg/advagg_css_cdn/advagg_css_cdn.info +++ b/sites/all/modules/contrib/advagg/advagg_css_cdn/advagg_css_cdn.info @@ -4,9 +4,9 @@ package = Advanced CSS/JS Aggregation core = 7.x dependencies[] = advagg -; Information added by Drupal.org packaging script on 2015-07-18 -version = "7.x-2.14" +; Information added by Drupal.org packaging script on 2016-03-29 +version = "7.x-2.18" core = "7.x" project = "advagg" -datestamp = "1437181444" +datestamp = "1459291143" diff --git a/sites/all/modules/contrib/advagg/advagg_css_cdn/advagg_css_cdn.module b/sites/all/modules/contrib/advagg/advagg_css_cdn/advagg_css_cdn.module index 3984d0074..2431bc1a4 100644 --- a/sites/all/modules/contrib/advagg/advagg_css_cdn/advagg_css_cdn.module +++ b/sites/all/modules/contrib/advagg/advagg_css_cdn/advagg_css_cdn.module @@ -19,7 +19,7 @@ define('ADVAGG_CSS_CDN_JQUERY_UI_VERSION', '1.8.7'); * Implements hook_css_alter(). */ function advagg_css_cdn_css_alter(&$css) { - // Only modify if jquery_update is not enabled, + // Only modify if jquery_update is not enabled. if (module_exists('jquery_update')) { return; } @@ -43,7 +43,8 @@ function advagg_css_cdn_css_alter(&$css) { $css[$name]['data'] = '//ajax.googleapis.com/ajax/libs/jqueryui/' . $jquery_ui_version . '/themes/base/jquery.' . $ui_mapping[$name] . '.css'; $css[$name]['type'] = 'external'; - // Fallback can not work do to "SecurityError: The operation is insecure." + // Fallback does not work do to + // "SecurityError: The operation is insecure.". } } } diff --git a/sites/all/modules/contrib/advagg/advagg_css_compress/advagg_css_compress.admin.inc b/sites/all/modules/contrib/advagg/advagg_css_compress/advagg_css_compress.admin.inc index f14139b1d..4114c0e8f 100644 --- a/sites/all/modules/contrib/advagg/advagg_css_compress/advagg_css_compress.admin.inc +++ b/sites/all/modules/contrib/advagg/advagg_css_compress/advagg_css_compress.admin.inc @@ -93,6 +93,11 @@ function advagg_css_compress_admin_settings_form($form, $form_state) { } } + // No css files are found. + if (empty($results)) { + $form['per_file_settings']['#description'] = t('No CSS files have been aggregated. You need to enable aggregation. No css files where found in the advagg_files table.'); + } + // Clear the cache bins on submit. $form['#submit'][] = 'advagg_css_compress_admin_settings_form_submit'; @@ -106,10 +111,8 @@ function advagg_css_compress_admin_settings_form($form, $form_state) { * Also remove default settings inside of the per_file_settings fieldgroup. */ function advagg_css_compress_admin_settings_form_submit($form, &$form_state) { - $cache_bins = advagg_flush_caches(); - foreach ($cache_bins as $bin) { - cache_clear_all('*', $bin, TRUE); - } + // Clear caches. + advagg_cache_clear_admin_submit(); // Get current defaults. $file_settings = variable_get('advagg_css_compressor_file_settings', array()); @@ -117,7 +120,7 @@ function advagg_css_compress_admin_settings_form_submit($form, &$form_state) { // Save per file settings. $new_settings = array(); foreach ($form_state['values'] as $key => $value) { - // Skip if not advagg_css_compressor_file_settings + // Skip if not advagg_css_compressor_file_settings. if (strpos($key, 'advagg_css_compressor_file_settings_') === FALSE) { continue; } diff --git a/sites/all/modules/contrib/advagg/advagg_css_compress/advagg_css_compress.info b/sites/all/modules/contrib/advagg/advagg_css_compress/advagg_css_compress.info index 57983d5a5..74f4d525e 100644 --- a/sites/all/modules/contrib/advagg/advagg_css_compress/advagg_css_compress.info +++ b/sites/all/modules/contrib/advagg/advagg_css_compress/advagg_css_compress.info @@ -6,9 +6,9 @@ dependencies[] = advagg configure = admin/config/development/performance/advagg/css-compress -; Information added by Drupal.org packaging script on 2015-07-18 -version = "7.x-2.14" +; Information added by Drupal.org packaging script on 2016-03-29 +version = "7.x-2.18" core = "7.x" project = "advagg" -datestamp = "1437181444" +datestamp = "1459291143" diff --git a/sites/all/modules/contrib/advagg/advagg_css_compress/yui/CSSMin.inc b/sites/all/modules/contrib/advagg/advagg_css_compress/yui/CSSMin.inc index c0c9bc378..b99970d5f 100644 --- a/sites/all/modules/contrib/advagg/advagg_css_compress/yui/CSSMin.inc +++ b/sites/all/modules/contrib/advagg/advagg_css_compress/yui/CSSMin.inc @@ -278,8 +278,11 @@ class CSSmin // Normalize all whitespace strings to single spaces. Easier to work with that way. $css = preg_replace('/\s+/', ' ', $css); - // Fix IE7 issue on matrix filters which browser accept whitespaces between Matrix parameters - $css = preg_replace_callback('/\s*filter\:\s*progid:DXImageTransform\.Microsoft\.Matrix\(([^\)]+)\)/', array($this, 'preserve_old_IE_specific_matrix_definition'), $css); + // preserve flex, keeping percentage even if 0 + $css = preg_replace_callback('/flex\s?:\s?((?:[0-9 ]*)\s?(?:px|em|auto|%)?(?:calc\(.*\))?)/i',array($this, 'replace_flex'),$css); + + // Fix IE7 issue on matrix filters which browser accept whitespaces between Matrix parameters + $css = preg_replace_callback('/\s*filter\:\s*progid:DXImageTransform\.Microsoft\.Matrix\(([^\)]+)\)/', array($this, 'preserve_old_IE_specific_matrix_definition'), $css); // Shorten & preserve calculations calc(...) since spaces are important $css = preg_replace_callback('/calc(\(((?:[^\(\)]+|(?1))*)\))/i', array($this, 'replace_calc'), $css); @@ -367,8 +370,9 @@ class CSSmin // Replace background-position:0; with background-position:0 0; // same for transform-origin + // same for background // Changing -webkit-mask-position: 0 0 to just a single 0 will result in the second parameter defaulting to 50% (center) - $css = preg_replace('/(background\-position|webkit-mask-position|(?:webkit|moz|o|ms|)\-?transform\-origin)\:0(;|\}| \!)/iS', '$1:0 0$2', $css); + $css = preg_replace('/(background|background\-position|webkit\-mask\-position|(?:webkit|moz|o|ms|)\-?transform\-origin)\:0(;|\}| \!)/iS', '$1:0 0$2', $css); // Shorten colors from rgb(51,102,153) to #336699, rgb(100%,0%,0%) to #ff0000 (sRGB color space) // Shorten colors from hsl(0, 100%, 50%) to #ff0000 (sRGB color space) @@ -479,7 +483,10 @@ class CSSmin if ($found_terminator) { $token = $this->str_slice($css, $start_index, $end_index); - $token = preg_replace('/\s+/', '', $token); + // remove whitespace, except if $token contains svg, which needs whitepace left as is + if (strpos($token,"preserved_tokens[] = $token; $preserver = 'url(' . self::TOKEN . (count($this->preserved_tokens) - 1) . '___)'; @@ -600,14 +607,20 @@ class CSSmin private function replace_calc($matches) { - $this->preserved_tokens[] = trim(preg_replace('/\s*([\*\/\(\),])\s*/', '$1', $matches[2])); + $this->preserved_tokens[] = preg_replace('/([\+\-]{1})\(/','$1 (',trim(preg_replace('/\s*([\*\/\(\),])\s*/', '$1', $matches[2]))); return 'calc('. self::TOKEN . (count($this->preserved_tokens) - 1) . '___' . ')'; } - private function preserve_old_IE_specific_matrix_definition($matches) - { - $this->preserved_tokens[] = $matches[1]; - return 'filter:progid:DXImageTransform.Microsoft.Matrix(' . self::TOKEN . (count($this->preserved_tokens) - 1) . '___' . ')'; + private function replace_flex($matches) + { + $this->preserved_tokens[] = trim($matches[1]); + return 'flex:'.self::TOKEN . (count($this->preserved_tokens) - 1) . '___'; + } + + private function preserve_old_IE_specific_matrix_definition($matches) + { + $this->preserved_tokens[] = $matches[1]; + return 'filter:progid:DXImageTransform.Microsoft.Matrix(' . self::TOKEN . (count($this->preserved_tokens) - 1) . '___' . ')'; } private function replace_keyframe_zero($matches) diff --git a/sites/all/modules/contrib/advagg/advagg_ext_compress/advagg_ext_compress.info b/sites/all/modules/contrib/advagg/advagg_ext_compress/advagg_ext_compress.info index e6f9a1df7..cba01faf8 100644 --- a/sites/all/modules/contrib/advagg/advagg_ext_compress/advagg_ext_compress.info +++ b/sites/all/modules/contrib/advagg/advagg_ext_compress/advagg_ext_compress.info @@ -6,9 +6,9 @@ dependencies[] = advagg configure = admin/config/development/performance/advagg/ext-compress -; Information added by Drupal.org packaging script on 2015-07-18 -version = "7.x-2.14" +; Information added by Drupal.org packaging script on 2016-03-29 +version = "7.x-2.18" core = "7.x" project = "advagg" -datestamp = "1437181444" +datestamp = "1459291143" diff --git a/sites/all/modules/contrib/advagg/advagg_ext_compress/advagg_ext_compress.module b/sites/all/modules/contrib/advagg/advagg_ext_compress/advagg_ext_compress.module index 88513f685..646920514 100644 --- a/sites/all/modules/contrib/advagg/advagg_ext_compress/advagg_ext_compress.module +++ b/sites/all/modules/contrib/advagg/advagg_ext_compress/advagg_ext_compress.module @@ -79,7 +79,7 @@ function advagg_ext_compress_execute_cmd($input_file, $ext = '') { if (empty($ext)) { $ext = strtolower(pathinfo($input_file, PATHINFO_EXTENSION)); if ($ext !== 'css' && $ext !== 'js') { - // Get the $ext from the database, + // Get the $ext from the database. $row = db_select('advagg_files', 'af') ->fields('af') ->condition('filename', $input_file) @@ -94,7 +94,7 @@ function advagg_ext_compress_execute_cmd($input_file, $ext = '') { } // Generate temp file. - $temp_file = drupal_tempnam('temporary://', 'file_advagg_'); + $temp_file = drupal_tempnam('temporary://', 'advagg_file_'); $new_temp_file = $temp_file . '.' . basename($input_file); @rename($temp_file, $new_temp_file); $output = advagg_get_relative_path($new_temp_file); @@ -126,7 +126,7 @@ function advagg_ext_compress_execute_cmd($input_file, $ext = '') { */ function advagg_ext_compress_js_compress(&$contents) { list(, $js_path) = advagg_get_root_files_dir(); - $temp_file = drupal_tempnam($js_path[0], 'file_advagg_'); + $temp_file = drupal_tempnam($js_path[0], 'advagg_file_'); $new_temp_file = $temp_file . '.js'; rename($temp_file, $new_temp_file); $temp_file_full = advagg_get_relative_path($new_temp_file); @@ -149,7 +149,7 @@ function advagg_ext_compress_js_compress(&$contents) { */ function advagg_ext_compress_css_compress(&$contents) { list($css_path) = advagg_get_root_files_dir(); - $temp_file = drupal_tempnam($css_path[0], 'file_advagg_'); + $temp_file = drupal_tempnam($css_path[0], 'advagg_file_'); $new_temp_file = $temp_file . '.css'; rename($temp_file, $new_temp_file); $temp_file_full = advagg_get_relative_path($new_temp_file); diff --git a/sites/all/modules/contrib/advagg/advagg_font/advagg_font.admin.inc b/sites/all/modules/contrib/advagg/advagg_font/advagg_font.admin.inc index 3ce8670ae..a3baa0154 100644 --- a/sites/all/modules/contrib/advagg/advagg_font/advagg_font.admin.inc +++ b/sites/all/modules/contrib/advagg/advagg_font/advagg_font.admin.inc @@ -16,16 +16,23 @@ function advagg_font_admin_settings_form() { drupal_set_title(t('AdvAgg: Async Font Loader')); $form = array(); - $package = drupal_http_request('https://cdn.rawgit.com/bramstein/fontfaceobserver/master/package.json'); - $package = json_decode($package->data); + $package = drupal_http_request('https://cdn.rawgit.com/bramstein/fontfaceobserver/master/package.json', array('timeout' => 7)); + if (!empty($package->data)) { + $package = json_decode($package->data); + } + else { + unset($package); + $package = new stdClass(); + $package->version = 0; + } $options = array( 0 => t('Disabled'), 6 => t('Externally load the latest from github (version: @version)', array('@version' => $package->version)), ); $description = t('This will use the fallback font until the font has been downloaded. See fontfaceobserver for more info.', array( - '@link' => 'https://github.com/bramstein/fontfaceobserver', - )); + '@link' => 'https://github.com/bramstein/fontfaceobserver', + )); if (function_exists('libraries_info')) { $lib_info = libraries_detect('fontfaceobserver'); if ($lib_info['installed']) { @@ -35,13 +42,13 @@ function advagg_font_admin_settings_form() { ); } else { - $description .= ' ' . t('To use fontfaceobserver locally fontfaceobserver needs to be placed inside the sites/all/libraries directory so sites/all/libraries/fontfaceobserver/fontfaceobserver.js can be found at that location.', array( + $description .= ' ' . t('To use fontfaceobserver locally fontfaceobserver needs to be placed inside the sites/all/libraries directory so sites/all/libraries/fontfaceobserver/fontfaceobserver.js and package.json can be found at that location.', array( '@url' => 'https://www.drupal.org/project/libraries', )); } } else { - $description .= ' ' . t('To use fontfaceobserver locally the libraries api module needs to be installed and then fontfaceobserver needs to be placed inside the sites/all/libraries directory so sites/all/libraries/fontfaceobserver/fontfaceobserver.js can be found at that location..', array( + $description .= ' ' . t('To use fontfaceobserver locally the libraries api module needs to be installed and then fontfaceobserver needs to be placed inside the sites/all/libraries directory so sites/all/libraries/fontfaceobserver/fontfaceobserver.js and package.json can be found at that location.', array( '@url' => 'https://www.drupal.org/project/libraries', )); } @@ -55,6 +62,32 @@ function advagg_font_admin_settings_form() { '#description' => $description, ); + $form['container'] = array( + '#type' => 'container', + '#states' => array( + 'invisible' => array( + ':input[name="advagg_font_fontfaceobserver"]' => array('value' => '0'), + ), + ), + ); + $form['container']['advagg_font_cookie'] = array( + '#type' => 'checkbox', + '#title' => t('Set a cookie so the flash of unstyled text (FOUT) only happens once.'), + '#default_value' => variable_get('advagg_font_cookie', ADVAGG_FONT_COOKIE), + '#description' => t('Cookies are name like @cookie. If this is a problem you can disable cookies from being set; if doing so the FOUT will happen on every page load.', array('@cookie' => 'advaggfont_pt-sans=PT Sans')), + ); + $form['container']['advagg_font_no_fout'] = array( + '#type' => 'checkbox', + '#title' => t('Prevent the Flash of Unstyled Text.'), + '#default_value' => variable_get('advagg_font_no_fout', ADVAGG_FONT_NO_FOUT), + '#description' => t('The font will not be changed unless the browser already has the font downloaded. Font gets downloaded on the first page view.', array('@cookie' => 'advaggfont_pt-sans=PT Sans')), + '#states' => array( + 'disabled' => array( + '#edit-advagg-font-cookie' => array('checked' => FALSE), + ), + ), + ); + // Get all css files and scan for quoted fonts. $form['fonts'] = array( '#type' => 'fieldset', @@ -77,9 +110,10 @@ function advagg_font_admin_settings_form() { $replacements = advagg_font_get_replacements_array($file_contents); if (!empty($replacements)) { $fonts = array(); - foreach ($replacements as $replacement) { - $fonts[$replacement[3]] = $replacement[5]; + foreach ($replacements as $key => $replacement) { + $fonts[$key . ' ' . $replacement[3]] = $replacement[5]; } + $form['fonts'][$row['filename_hash']] = array( '#markup' => '
' . t('%file - @replacements
', array( '@replacements' => str_ireplace('array', '', print_r($fonts, TRUE)), @@ -95,12 +129,20 @@ function advagg_font_admin_settings_form() { $form['advagg_font_fontfaceobserver']['#default_value'] = 0; $form['advagg_font_fontfaceobserver']['#disabled'] = TRUE; - $form['fonts'] = array( - '#type' => 'fieldset', - '#title' => t('No CSS files with external fonts found.'), - '#description' => t('Currently this module is not doing anything. Recommend uninstalling it as advagg is not processing any css files that use an external font file.'), - ); - + if (empty($results)) { + $form['fonts'] = array( + '#type' => 'fieldset', + '#title' => t('No CSS files have been aggregated.'), + '#description' => t('You need to enable aggregation. No css files where found in the advagg_files table.'), + ); + } + else { + $form['fonts'] = array( + '#type' => 'fieldset', + '#title' => t('No CSS files with external fonts found.'), + '#description' => t('Currently this module is not doing anything. Recommend uninstalling it as advagg is not processing any css files that use an external font file.'), + ); + } } // Clear the cache bins on submit. @@ -114,8 +156,15 @@ function advagg_font_admin_settings_form() { * Clear out the advagg cache bin when the save configuration button is pressed. */ function advagg_font_admin_settings_form_submit($form, &$form_state) { - $cache_bins = advagg_flush_caches(); - foreach ($cache_bins as $bin) { - cache_clear_all('*', $bin, TRUE); + // Clear caches. + advagg_cache_clear_admin_submit(); + + // Disable cookie if ffo is disabled. + if (empty($form_state['values']['advagg_font_fontfaceobserver'])) { + $form_state['values']['advagg_font_cookie'] = 0; + } + // Disable no fout if cookies are disabled. + if (empty($form_state['values']['advagg_font_cookie'])) { + $form_state['values']['advagg_font_no_fout'] = 0; } } diff --git a/sites/all/modules/contrib/advagg/advagg_font/advagg_font.advagg.inc b/sites/all/modules/contrib/advagg/advagg_font/advagg_font.advagg.inc index c65b12746..6ea54aa22 100644 --- a/sites/all/modules/contrib/advagg/advagg_font/advagg_font.advagg.inc +++ b/sites/all/modules/contrib/advagg/advagg_font/advagg_font.advagg.inc @@ -34,17 +34,19 @@ function advagg_font_advagg_get_info_on_files_alter(&$return, $cached_data, $byp continue; } // Get the file contents. - $file_contents = file_get_contents($info['data']); - // Get font names. - $replacements = advagg_font_get_replacements_array($file_contents); + $file_contents = @file_get_contents($info['data']); + if (!empty($file_contents)) { + // Get font names. + $replacements = advagg_font_get_replacements_array($file_contents); - // Remove old values. - if (isset($info['advagg_font'])) { - unset($info['advagg_font']); - } - // Add in new values. - foreach ($replacements as $replace) { - $info['advagg_font'][$replace[4]] = $replace[3]; + // Remove old values. + if (isset($info['advagg_font'])) { + unset($info['advagg_font']); + } + // Add in new values. + foreach ($replacements as $replace) { + $info['advagg_font'][$replace[4]] = str_replace(array('"', "'"), '', $replace[3]); + } } } unset($info); diff --git a/sites/all/modules/contrib/advagg/advagg_font/advagg_font.info b/sites/all/modules/contrib/advagg/advagg_font/advagg_font.info index 920b85df4..9698508c5 100644 --- a/sites/all/modules/contrib/advagg/advagg_font/advagg_font.info +++ b/sites/all/modules/contrib/advagg/advagg_font/advagg_font.info @@ -5,9 +5,9 @@ dependencies[] = advagg package = Advanced CSS/JS Aggregation configure = admin/config/development/performance/advagg/font -; Information added by Drupal.org packaging script on 2015-07-18 -version = "7.x-2.14" +; Information added by Drupal.org packaging script on 2016-03-29 +version = "7.x-2.18" core = "7.x" project = "advagg" -datestamp = "1437181444" +datestamp = "1459291143" diff --git a/sites/all/modules/contrib/advagg/advagg_font/advagg_font.install b/sites/all/modules/contrib/advagg/advagg_font/advagg_font.install new file mode 100644 index 000000000..80ac83fe4 --- /dev/null +++ b/sites/all/modules/contrib/advagg/advagg_font/advagg_font.install @@ -0,0 +1,78 @@ + 7)); + if (!empty($package->data)) { + $package = json_decode($package->data); + } + else { + unset($package); + $package = new stdClass(); + $package->version = 0; + } + + $lib_info = array(); + if (function_exists('libraries_info')) { + $lib_info = libraries_detect('fontfaceobserver'); + } + + if ( variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0 + && function_exists('libraries_info') + && variable_get('advagg_font_fontfaceobserver', ADVAGG_FONT_FONTFACEOBSERVER) == 6 + ) { + $requirements['advagg_font_rawgithub'] = array( + 'title' => $t('AdvAgg Font - Host locally'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('The fontfaceobserver.js file should be local for better performance.'), + 'description' => empty($lib_info['version']) ? + $t('Directions on how to install it can be found on the Async Font Loader settings page', array('@url' => url('admin/config/development/performance/advagg/font'))) : + $t('Please go to the Async Font Loader settings page and choose inline or local.', array('@url' => url('admin/config/development/performance/advagg/font'))), + ); + } + + if ( isset($lib_info['version']) + && !empty($package->version) + && version_compare($package->version, $lib_info['version']) >= 1 + ) { + $requirements['advagg_font_old_library'] = array( + 'title' => $t('AdvAgg Font - Version is out of date'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('Local version of font face observer is old'), + 'description' => $t('Please upgrade your current version (@local-ver) of font face observer to the latest (@remote-ver). The local copy of font face observer is located in the %dir directory', array( + '@local-ver' => $lib_info['version'], + '@remote-ver' => $package->version, + '%dir' => $lib_info['library path'], + '@url' => 'https://github.com/bramstein/fontfaceobserver', + )), + ); + } + + if (empty($requirements)) { + $requirements['advagg_font'] = array( + 'title' => $t('AdvAgg Font'), + 'severity' => REQUIREMENT_OK, + 'value' => $t('OK'), + 'description' => empty($package->version) ? $t('The font face observer JavaScript should be working.') : $t('The font face observer JavaScript library is up to date (%version).', array('%version' => $package->version)), + ); + } + + return $requirements; +} diff --git a/sites/all/modules/contrib/advagg/advagg_font/advagg_font.js b/sites/all/modules/contrib/advagg/advagg_font/advagg_font.js index 5d1856547..49f3b153e 100644 --- a/sites/all/modules/contrib/advagg/advagg_font/advagg_font.js +++ b/sites/all/modules/contrib/advagg/advagg_font/advagg_font.js @@ -8,12 +8,13 @@ /** * Run the check. * - * @param string key + * @param {string} key * The class name to add to the html tag. - * @param string value + * @param {string} value * The font name. */ function advagg_run_check(key, value) { + "use strict"; // Only run if window.FontFaceObserver is defined. if (window.FontFaceObserver) { new window.FontFaceObserver(value).check().then(function () { @@ -21,19 +22,24 @@ function advagg_run_check(key, value) { key = key.replace(/[^a-zA-Z0-9\-]/g, ''); // Set Class. - window.document.documentElement.className += ' ' + key; + if (Drupal.settings.advagg_font_no_fout != 1) { + window.document.documentElement.className += ' ' + key; + } // Set Cookie for a day. - expire_date = new Date(new Date().getTime() + 86400 * 1000); - document.cookie = 'advaggfont_' + key + '=' + value + ';' - + ' expires=' + expire_date.toGMTString() + ';' - + ' path=/;' - + ' domain=.' + document.location.hostname + ';'; - }, function() {}); + if (Drupal.settings.advagg_font_cookie == 1) { + var expire_date = new Date(new Date().getTime() + 86400 * 1000); + document.cookie = 'advaggfont_' + key + '=' + value + ';' + + ' expires=' + expire_date.toGMTString() + ';' + + ' path=/;' + + ' domain=.' + document.location.hostname + ';'; + } + }, function () {} + ); } else { // Try again in 100 ms. - window.setTimeout(function() { + window.setTimeout(function () { advagg_run_check(key, value); }, 100); } @@ -43,6 +49,7 @@ function advagg_run_check(key, value) { * Get the list of fonts to check for. */ function advagg_font_add_font_classes_on_load() { + "use strict"; for (var key in Drupal.settings.advagg_font) { var html_class = (' ' + window.document.documentElement.className + ' ').indexOf(' ' + key + ' '); // If the class already exists in the html element do nothing. @@ -57,6 +64,7 @@ function advagg_font_add_font_classes_on_load() { * Make sure jQuery and Drupal.settings are defined before running. */ function advagg_font_check() { + "use strict"; if (window.jQuery && window.Drupal && window.Drupal.settings) { advagg_font_add_font_classes_on_load(); } diff --git a/sites/all/modules/contrib/advagg/advagg_font/advagg_font.module b/sites/all/modules/contrib/advagg/advagg_font/advagg_font.module index cac69cfa5..63561f56b 100644 --- a/sites/all/modules/contrib/advagg/advagg_font/advagg_font.module +++ b/sites/all/modules/contrib/advagg/advagg_font/advagg_font.module @@ -11,30 +11,68 @@ */ define('ADVAGG_FONT_FONTFACEOBSERVER', 0); +/** + * Default value to use a cookie in order to prevent the FOUT. + */ +define('ADVAGG_FONT_COOKIE', 1); + +/** + * Default value to only replace the font if it's been downloaded. + */ +define('ADVAGG_FONT_NO_FOUT', 0); + // Core hook implementations. /** - * Implements hook_page_build(). + * Implements hook_module_implements_alter(). + */ +function advagg_font_module_implements_alter(&$implementations, $hook) { + // Move advagg to the bottom. + if ($hook === 'page_alter' && array_key_exists('advagg_font', $implementations)) { + $item = $implementations['advagg_font']; + unset($implementations['advagg_font']); + $implementations['advagg_font'] = $item; + } +} + +/** + * Implements hook_page_alter(). */ -function advagg_font_page_build(&$page) { +function advagg_font_page_alter(&$page) { + // The $page variable is not an array; or the content key does not exist. + if (!is_array($page) || !isset($page['content'])) { + return; + } $advagg_font_ffo = variable_get('advagg_font_fontfaceobserver', ADVAGG_FONT_FONTFACEOBSERVER); + // Fontface Observer is disabled. if (empty($advagg_font_ffo)) { return; } + $page['content']['#attached']['js'][] = array( + 'type' => 'setting', + 'data' => array( + 'advagg_font_cookie' => variable_get('advagg_font_cookie', ADVAGG_FONT_COOKIE), + 'advagg_font_no_fout' => variable_get('advagg_font_no_fout', ADVAGG_FONT_NO_FOUT), + ), + ); + // Add inline script for reading the cookies and adding the fonts already // loaded to the html class. - $inline_script_min = 'var fonts=document.cookie.split("advagg");for(var key in fonts){var font=fonts[key].split("="),pos=font[0].indexOf("font_");-1!==pos&&(window.document.documentElement.className+=" "+font[0].substr(5).replace(/[^a-zA-Z0-9\-]/g,""))}'; - $page['content']['#attached']['js']['advagg_font.inline.js'] = array( - 'type' => 'inline', - 'group' => JS_LIBRARY - 1, - 'weight' => -50000, - 'scope_lock' => TRUE, - 'movable' => FALSE, - 'no_defer' => TRUE, - 'data' => $inline_script_min, - ); + if (variable_get('advagg_font_cookie', ADVAGG_FONT_COOKIE)) { + $inline_script_min = 'var fonts=document.cookie.split("advagg");for(var key in fonts){var font=fonts[key].split("="),pos=font[0].indexOf("font_");-1!==pos&&(window.document.documentElement.className+=" "+font[0].substr(5).replace(/[^a-zA-Z0-9\-]/g,""))}'; + $page['content']['#attached']['js']['advagg_font.inline.js'] = array( + 'type' => 'inline', + 'group' => JS_LIBRARY - 1, + 'weight' => -50000, + 'scope_lock' => TRUE, + 'movable' => FALSE, + 'no_defer' => TRUE, + 'data' => $inline_script_min, + ); + } - // Add advagg_font.js; sets cookie once a font has been downloaded. + // Add advagg_font.js; sets cookie and changes the class of the top level + // element once a font has been downloaded. $file_path = drupal_get_path('module', 'advagg_font') . '/advagg_font.js'; $page['content']['#attached']['js'][$file_path] = array( 'async' => TRUE, @@ -116,9 +154,9 @@ function advagg_font_libraries_info() { 'name' => 'fontfaceobserver', 'vendor url' => 'https://github.com/bramstein/fontfaceobserver', 'download url' => 'https://github.com/bramstein/fontfaceobserver/archive/master.zip', - // 1.50. : "version": "1.5.0", 'version arguments' => array( 'file' => 'package.json', + // 1.50. : "version": "1.5.0". 'pattern' => '/"version":\\s+"([0-9\.]+)"/', 'lines' => 10, ), @@ -158,51 +196,85 @@ function advagg_font_get_replacements_array($css_string) { // Get the CSS that contains a font-family rule. $length = strlen($css_string); $property = 'font-family'; - $last_position = 0; + $property_position = 0; $replacements = array(); - while (($last_position = strpos($css_string, $property, $last_position)) !== FALSE) { - // Get closing bracket. - $end = strpos($css_string, '}', $last_position); - if ($end === FALSE) { - $end = $length; - } - - // Get position of the last closing bracket (start of this section). - $start = strrpos($css_string, '}', -($length - $last_position)); + while (($property_position = strpos($css_string, $property, $property_position)) !== FALSE) { + // Get position of the last closing bracket plus 1 (start of this section). + $start = strrpos($css_string, '}', -($length - $property_position)); if ($start === FALSE) { + // Property is in the first selector and a declaration block (full rule + // set). $start = 0; } else { + // Add one to start after the }. $start++; } - // Get closing ; in order to get the end of the declaration. - $declaration_end = strpos($css_string, ';', $last_position); + // Get closing bracket (end of this section). + $end = strpos($css_string, '}', $property_position); + if ($end === FALSE) { + // The end is the end of this file. + $end = $length; + } + + // Get closing ; in order to get the end of the declaration of the property. + $declaration_end = strpos($css_string, ';', $property_position); if ($declaration_end > $end) { $declaration_end = $end; } + // Add one in order to capture the } when we ge the full rule set. $end++; - // Get values. - $start_of_values = strpos($css_string, ':', $last_position); + // Get values assigned to this property. + $start_of_values = strpos($css_string, ':', $property_position); $values_string = substr($css_string, $start_of_values + 1, $declaration_end - ($start_of_values + 1)); // Parse values string into an array of values. $values_array = explode(',', $values_string); + // Advance position for the next run of the while loop. + $property_position = $end; + // Values array has more than 1 value and first element is a quoted string. if (count($values_array) > 1 && (strpos($values_array[0], '"') !== FALSE || strpos($values_array[0], "'") !== FALSE)) { - // Remove first value and render css rule. + // Save the first value to a variable and trim it. $removed_value_original = trim($values_array[0]); - $next_value_original = trim($values_array[1]); - $removed_value = strtolower(trim(str_replace(array('"', "'"), '', $removed_value_original))); - $removed_value = str_replace(' ', '-', $removed_value); - unset($values_array[0]); - $new_values_array = implode(',', $values_array); + // Get value as a classname. Remove quotes, trim, lowercase, and replace + // spaces with dashes. + $removed_value_classname = strtolower(trim(str_replace(array('"', "'"), '', $removed_value_original))); + $removed_value_classname = str_replace(' ', '-', $removed_value_classname); + // Remove value if it contains a quote. + foreach ($values_array as $key => $value) { + if (strpos($value, '"') !== FALSE || strpos($value, "'") !== FALSE) { + unset($values_array[$key]); + } + else { + break; + } + } + if (empty($values_array)) { + // No unquoted values left; do not modify the css. + continue; + } + // Rezero the keys. + $values_array = array_values($values_array); + // Save next value. + $next_value_original = trim($values_array[0]); + // Create the values string. + $new_values_string = implode(',', $values_array); // Get all selectors. $end_of_selectors = strpos($css_string, '{', $start); $selectors = substr($css_string, $start, $end_of_selectors - $start); + // Ensure selectors is not a media query. + if (strpos($selectors, "@media") !== FALSE) { + // Move the start to the end of the media query. + $start = $end_of_selectors + 1; + // Get the selectors again. + $end_of_selectors = strpos($css_string, '{', $start); + $selectors = substr($css_string, $start, $end_of_selectors - $start); + } // From advagg_load_stylesheet_content(). // Perform some safe CSS optimizations. @@ -222,14 +294,14 @@ function advagg_font_get_replacements_array($css_string) { // Add css class to all the selectors. $selectors_array = explode(',', $selectors_stripped); foreach ($selectors_array as &$selector) { - $selector = ' .' . $removed_value . ' ' . $selector; + $selector = ' .' . $removed_value_classname . ' ' . $selector; } $new_selectors = implode(',', $selectors_array); // Get full rule set. $full_rule_set = substr($css_string, $start, $end - $start); // Replace values. - $new_values_full_rule_set = str_replace($values_string, $new_values_array, $full_rule_set); + $new_values_full_rule_set = str_replace($values_string, $new_values_string, $full_rule_set); // Add in old rule set with new selectors. $new_selectors_full_rule_set = $new_selectors . '{' . $property . ': ' . $values_string . ';}'; @@ -239,13 +311,10 @@ function advagg_font_get_replacements_array($css_string) { $new_values_full_rule_set, $new_selectors_full_rule_set, $removed_value_original, - $removed_value, + $removed_value_classname, $next_value_original, ); } - - // Advance position. - $last_position = $end; } return $replacements; } diff --git a/sites/all/modules/contrib/advagg/advagg_js_cdn/advagg_js_cdn.info b/sites/all/modules/contrib/advagg/advagg_js_cdn/advagg_js_cdn.info index f62cef451..38901e206 100644 --- a/sites/all/modules/contrib/advagg/advagg_js_cdn/advagg_js_cdn.info +++ b/sites/all/modules/contrib/advagg/advagg_js_cdn/advagg_js_cdn.info @@ -4,9 +4,9 @@ package = Advanced CSS/JS Aggregation core = 7.x dependencies[] = advagg -; Information added by Drupal.org packaging script on 2015-07-18 -version = "7.x-2.14" +; Information added by Drupal.org packaging script on 2016-03-29 +version = "7.x-2.18" core = "7.x" project = "advagg" -datestamp = "1437181444" +datestamp = "1459291143" diff --git a/sites/all/modules/contrib/advagg/advagg_js_cdn/advagg_js_cdn.module b/sites/all/modules/contrib/advagg/advagg_js_cdn/advagg_js_cdn.module index 74fefb175..b0f3c8c77 100644 --- a/sites/all/modules/contrib/advagg/advagg_js_cdn/advagg_js_cdn.module +++ b/sites/all/modules/contrib/advagg/advagg_js_cdn/advagg_js_cdn.module @@ -34,7 +34,7 @@ define('ADVAGG_JS_CDN_COMPRESSION', TRUE); * Implements hook_js_alter(). */ function advagg_js_cdn_js_alter(&$javascript) { - // Only modify if jquery_update is not enabled, + // Only modify if jquery_update is not enabled. if (module_exists('jquery_update')) { return; } diff --git a/sites/all/modules/contrib/advagg/advagg_js_cdn/js/jquery-ui.js b/sites/all/modules/contrib/advagg/advagg_js_cdn/js/jquery-ui.js index ea54943b9..fd3e91c92 100644 --- a/sites/all/modules/contrib/advagg/advagg_js_cdn/js/jquery-ui.js +++ b/sites/all/modules/contrib/advagg/advagg_js_cdn/js/jquery-ui.js @@ -1,4 +1,7 @@ // @codingStandardsIgnoreFile +/* jshint ignore:start */ +/*eslint-disable */ + /*! * jQuery UI 1.8.7 * @@ -11510,3 +11513,5 @@ $.extend( $.ui.tabs.prototype, { }); })( jQuery ); +/*eslint-enable */ +/* jshint ignore:end */ diff --git a/sites/all/modules/contrib/advagg/advagg_js_compress/advagg_js_compress.admin.inc b/sites/all/modules/contrib/advagg/advagg_js_compress/advagg_js_compress.admin.inc index 050827b62..6449a0bb8 100644 --- a/sites/all/modules/contrib/advagg/advagg_js_compress/advagg_js_compress.admin.inc +++ b/sites/all/modules/contrib/advagg/advagg_js_compress/advagg_js_compress.admin.inc @@ -64,12 +64,17 @@ function advagg_js_compress_admin_settings_form($form, $form_state) { ), ); $form['advagg_js_compress_add_license'] = array( - '#type' => 'checkbox', - '#title' => t('Add licensing comments'), + '#type' => 'radios', + '#title' => t('Licensing comments'), '#default_value' => variable_get('advagg_js_compress_add_license', ADVAGG_JS_COMPRESS_ADD_LICENSE), - '#description' => t("If unchecked, the Advanced Aggregation module's licensing comments - will be omitted from the aggregated files. Omitting the comments will produce somewhat better scores in - some automated security scans but otherwise should not affect your site. These are included by default in order to better follow the spirit of the GPL by providing the source for javascript files."), + '#description' => t("Stripping everything will produce somewhat better scores in + some automated scans but otherwise should not affect your site. Providing a link to the original source and keeping important comments is enabled by default in order to better follow the spirit of the GPL by linking to the original unminified javascript source files."), + '#options' => array( + 0 => t('Strip everything'), + 1 => t('Provide a link to the original source as a comment'), + 2 => t('Keep important comments if the minifier supports it'), + 3 => t('Do both; try to keep important comments and provide a link'), + ), ); $options[-1] = t('Default'); @@ -111,6 +116,11 @@ function advagg_js_compress_admin_settings_form($form, $form_state) { } } + // No js files are found. + if (empty($results)) { + $form['per_file_settings']['#description'] = t('No JS files have been aggregated. You need to enable aggregation. No js files where found in the advagg_files table.'); + } + // Clear the cache bins on submit. $form['#submit'][] = 'advagg_js_compress_admin_settings_form_submit'; @@ -124,10 +134,7 @@ function advagg_js_compress_admin_settings_form($form, $form_state) { * Also remove default settings inside of the per_file_settings fieldgroup. */ function advagg_js_compress_admin_settings_form_submit($form, &$form_state) { - $cache_bins = advagg_flush_caches(); - foreach ($cache_bins as $bin) { - cache_clear_all('*', $bin, TRUE); - } + advagg_cache_clear_admin_submit(); // Get current defaults. $file_settings = variable_get('advagg_js_compressor_file_settings', array()); @@ -135,7 +142,7 @@ function advagg_js_compress_admin_settings_form_submit($form, &$form_state) { // Save per file settings. $new_settings = array(); foreach ($form_state['values'] as $key => $value) { - // Skip if not advagg_js_compressor_file_settings + // Skip if not advagg_js_compressor_file_settings. if (strpos($key, 'advagg_js_compressor_file_settings_') === FALSE) { continue; } diff --git a/sites/all/modules/contrib/advagg/advagg_js_compress/advagg_js_compress.advagg.inc b/sites/all/modules/contrib/advagg/advagg_js_compress/advagg_js_compress.advagg.inc index b3de7f690..32130d6da 100644 --- a/sites/all/modules/contrib/advagg/advagg_js_compress/advagg_js_compress.advagg.inc +++ b/sites/all/modules/contrib/advagg/advagg_js_compress/advagg_js_compress.advagg.inc @@ -300,7 +300,8 @@ function advagg_js_compress_prep(&$contents, $filename, array $aggregate_setting && $semicolon_count > 10 && $semicolon_count > (substr_count($contents, "\n", strpos($contents, ';')) * 5) ) { - if ($add_licensing && variable_get('advagg_js_compress_add_license', ADVAGG_JS_COMPRESS_ADD_LICENSE)) { + $add_license_setting = isset($aggregate_settings['variables']['advagg_js_compress_add_license']) ? $aggregate_settings['variables']['advagg_js_compress_add_license'] : variable_get('advagg_js_compress_add_license', ADVAGG_JS_COMPRESS_ADD_LICENSE); + if ($add_licensing && ($add_license_setting == 1 || $add_license_setting == 3)) { $url = file_create_url($filename); $contents = "/* Source and licensing information for the line(s) below can be found at $url. */\n" . $contents . ";\n/* Source and licensing information for the above line(s) can be found at $url. */\n"; } @@ -323,6 +324,20 @@ function advagg_js_compress_prep(&$contents, $filename, array $aggregate_setting } } + // Jsmin doesn't handle multi-byte characters before version 2, fall back to + // different compressor if jsmin version < 2 and $contents contains multi- + // byte characters. + if ($compressor == 3 && (version_compare(phpversion('jsmin'), '2.0.0', '<') &&advagg_js_compress_string_contains_multibyte_characters($contents))) { + if (defined('PHP_VERSION_ID') && constant('PHP_VERSION_ID') >= 50300) { + $compressor = 5; + watchdog('advagg_js_compress', 'The currently installed jsmin version does not handle multibyte characters, you may consider to upgrade the jsmin extension. Using JSqueeze fallback.'); + } + else { + $compressor = 1; + watchdog('advagg_js_compress', 'The currently installed jsmin version does not handle multibyte characters, you may consider to upgrade the jsmin extension. Using JSmin+ fallback.'); + } + } + // Try cache. $info = advagg_get_info_on_files(array($filename), FALSE, FALSE); $info = $info[$filename]; @@ -337,19 +352,25 @@ function advagg_js_compress_prep(&$contents, $filename, array $aggregate_setting $contents = str_replace(pack("CCC", 0xef, 0xbb, 0xbf), "", $contents); // Use the compressor. list(, , , $functions) = advagg_js_compress_configuration(); - if ($compressor == 1) { - advagg_js_compress_jsminplus($contents, $log_errors); - } - elseif (isset($functions[$compressor])) { + if (isset($functions[$compressor])) { $run = $functions[$compressor]; if (function_exists($run)) { - $run($contents); + $run($contents, $log_errors, $aggregate_settings); } } else { return; } + // Under some unknown/rare circumstances, JSMin and JSqueeze can add 2-3 + // extraneous/wrong chars at the end of the string. This work-around will + // remove these chars if necessary. See https://www.drupal.org/node/2627468. + // Also see https://github.com/sqmk/pecl-jsmin/issues/46. + $a = strrpos($contents, ';'); + $b = strrpos($contents, '}'); + $c = strrpos($contents, ')'); + $contents = substr($contents, 0, 1 + max($a, $b, $c)); + // Ensure that $contents ends with ; or }. if (strpbrk(substr(trim($contents), -1), ';}') === FALSE) { // ; or } not found, add in ; to the end of $contents. @@ -369,6 +390,7 @@ function advagg_js_compress_prep(&$contents, $filename, array $aggregate_setting if ($test_ratios) { $aggregate_settings['variables']['advagg_js_compress_max_ratio'] = isset($aggregate_settings['variables']['advagg_js_compress_max_ratio']) ? $aggregate_settings['variables']['advagg_js_compress_max_ratio'] : variable_get('advagg_js_compress_max_ratio', ADVAGG_JS_COMPRESS_MAX_RATIO); + $add_license_setting = isset($aggregate_settings['variables']['advagg_js_compress_add_license']) ? $aggregate_settings['variables']['advagg_js_compress_add_license'] : variable_get('advagg_js_compress_add_license', ADVAGG_JS_COMPRESS_ADD_LICENSE); // Make sure the returned string is not empty or has a VERY high // compression ratio. if ( empty($contents) @@ -378,13 +400,33 @@ function advagg_js_compress_prep(&$contents, $filename, array $aggregate_setting ) { $contents = $contents_before; } - elseif ($add_licensing && variable_get('advagg_js_compress_add_license', ADVAGG_JS_COMPRESS_ADD_LICENSE)) { + elseif ($add_licensing && ($add_license_setting == 1 || $add_license_setting == 3)) { $url = file_create_url($filename); $contents = "/* Source and licensing information for the line(s) below can be found at $url. */\n" . $contents . ";\n/* Source and licensing information for the above line(s) can be found at $url. */\n"; } } } +/** + * Checks if string contains multibyte characters. + * + * @param string $string + * String to check. + * + * @return bool + * TRUE if string contains multibyte character. + */ +function advagg_js_compress_string_contains_multibyte_characters($string) { + // Check if there are multy-byte characters: If the UTF-8 encoded string has + // multybytes strlen() will return a byte-count greater than the actual + // character count, returned by drupal_strlen(). + if (strlen($string) == drupal_strlen($string)) { + return FALSE; + } + + return TRUE; +} + /** * Compress a JS string using jsmin+. * @@ -418,7 +460,10 @@ function advagg_js_compress_jsminplus(&$contents, $log_errors = TRUE) { catch (Exception $e) { // Log the exception thrown by JSMin+ and roll back to uncompressed content. if ($log_errors) { - watchdog('advagg_js_compress', $e->getMessage() . '
' . $contents_before . '
', NULL, WATCHDOG_WARNING); + watchdog('advagg_js_compress', '@message
 @contents 
', array( + '@message' => $e->getMessage(), + '@contents' => $contents_before, + ), WATCHDOG_WARNING); } $contents = $contents_before; } diff --git a/sites/all/modules/contrib/advagg/advagg_js_compress/advagg_js_compress.info b/sites/all/modules/contrib/advagg/advagg_js_compress/advagg_js_compress.info index 39fbb5e54..f1e77587d 100644 --- a/sites/all/modules/contrib/advagg/advagg_js_compress/advagg_js_compress.info +++ b/sites/all/modules/contrib/advagg/advagg_js_compress/advagg_js_compress.info @@ -6,9 +6,9 @@ dependencies[] = advagg configure = admin/config/development/performance/advagg/js-compress -; Information added by Drupal.org packaging script on 2015-07-18 -version = "7.x-2.14" +; Information added by Drupal.org packaging script on 2016-03-29 +version = "7.x-2.18" core = "7.x" project = "advagg" -datestamp = "1437181444" +datestamp = "1459291143" diff --git a/sites/all/modules/contrib/advagg/advagg_js_compress/advagg_js_compress.module b/sites/all/modules/contrib/advagg/advagg_js_compress/advagg_js_compress.module index f452e06b6..cbd9fa9db 100644 --- a/sites/all/modules/contrib/advagg/advagg_js_compress/advagg_js_compress.module +++ b/sites/all/modules/contrib/advagg/advagg_js_compress/advagg_js_compress.module @@ -43,7 +43,7 @@ define('ADVAGG_JS_COMPRESSOR_FILE_SETTINGS', -1); /** * Default value to if inline compression is used if page is not cacheable. */ -define('ADVAGG_JS_COMPRESS_ADD_LICENSE', TRUE); +define('ADVAGG_JS_COMPRESS_ADD_LICENSE', 3); /** * Implements hook_menu(). @@ -75,6 +75,7 @@ function advagg_js_compress_advagg_current_hooks_hash_array_alter(&$aggregate_se $aggregate_settings['variables']['advagg_js_compress_packer'] = variable_get('advagg_js_compress_packer', ADVAGG_JS_COMPRESS_PACKER); $aggregate_settings['variables']['advagg_js_compress_max_ratio'] = variable_get('advagg_js_compress_max_ratio', ADVAGG_JS_COMPRESS_MAX_RATIO); $aggregate_settings['variables']['advagg_js_compressor_file_settings'] = variable_get('advagg_js_compressor_file_settings', array()); + $aggregate_settings['variables']['advagg_js_compress_add_license'] = variable_get('advagg_js_compress_add_license', ADVAGG_JS_COMPRESS_ADD_LICENSE); } /** diff --git a/sites/all/modules/contrib/advagg/advagg_js_compress/advagg_js_compress.php53.inc b/sites/all/modules/contrib/advagg/advagg_js_compress/advagg_js_compress.php53.inc index eb1874ab0..b2f68f5a1 100644 --- a/sites/all/modules/contrib/advagg/advagg_js_compress/advagg_js_compress.php53.inc +++ b/sites/all/modules/contrib/advagg/advagg_js_compress/advagg_js_compress.php53.inc @@ -38,7 +38,10 @@ function advagg_js_compress_jshrink(&$contents, $log_errors = TRUE) { catch (Exception $e) { // Log the JShrink exception and rollback to uncompressed content. if ($log_errors) { - watchdog('advagg_js_compress', $e->getMessage() . '
' . $contents_before . '
', NULL, WATCHDOG_WARNING); + watchdog('advagg_js_compress', '@message
 @contents 
', array( + '@message' => $e->getMessage(), + '@contents' => $contents_before, + ), WATCHDOG_WARNING); } $contents = $contents_before; } @@ -52,8 +55,10 @@ function advagg_js_compress_jshrink(&$contents, $log_errors = TRUE) { * Javascript string. * @param bool $log_errors * FALSE to disable logging to watchdog on failure. + * @param array $aggregate_settings + * The aggregate_settings array. */ -function advagg_js_compress_jsqueeze(&$contents, $log_errors = TRUE) { +function advagg_js_compress_jsqueeze(&$contents, $log_errors = TRUE, array $aggregate_settings = array()) { $contents_before = $contents; // Only include jshrink.inc if the Patchwork\JSqueeze class doesn't exist. @@ -66,12 +71,17 @@ function advagg_js_compress_jsqueeze(&$contents, $log_errors = TRUE) { } ob_start(); try { + $add_license_setting = isset($aggregate_settings['variables']['advagg_js_compress_add_license']) ? $aggregate_settings['variables']['advagg_js_compress_add_license'] : variable_get('advagg_js_compress_add_license', ADVAGG_JS_COMPRESS_ADD_LICENSE); + $keep_important_comments = FALSE; + if ($add_license_setting == 2 || $add_license_setting == 3) { + $keep_important_comments = TRUE; + } // Minify the contents of the aggregated file. $jz = new Patchwork\JSqueeze(); $contents = $jz->squeeze( $contents, TRUE, - !variable_get('advagg_js_compress_add_license', ADVAGG_JS_COMPRESS_ADD_LICENSE), + $keep_important_comments, FALSE ); @@ -84,7 +94,10 @@ function advagg_js_compress_jsqueeze(&$contents, $log_errors = TRUE) { catch (Exception $e) { // Log the JSqueeze exception and rollback to uncompressed content. if ($log_errors) { - watchdog('advagg_js_compress', $e->getMessage() . '
' . $contents_before . '
', NULL, WATCHDOG_WARNING); + watchdog('advagg_js_compress', '@message
 @contents 
', array( + '@message' => $e->getMessage(), + '@contents' => $contents_before, + ), WATCHDOG_WARNING); } $contents = $contents_before; } diff --git a/sites/all/modules/contrib/advagg/advagg_js_compress/jsqueeze.inc b/sites/all/modules/contrib/advagg/advagg_js_compress/jsqueeze.inc index 0c95b2a75..0cda0b637 100644 --- a/sites/all/modules/contrib/advagg/advagg_js_compress/jsqueeze.inc +++ b/sites/all/modules/contrib/advagg/advagg_js_compress/jsqueeze.inc @@ -20,7 +20,7 @@ // @ignore :file /* - * Copyright (C) 2015 Nicolas Grekas - p@tchwork.com + * Copyright (C) 2016 Nicolas Grekas - p@tchwork.com * * This library is free software; you can redistribute it and/or modify it * under the terms of the (at your option): @@ -99,14 +99,18 @@ class JSqueeze $varRx = '(?:[a-zA-Z_$])[a-zA-Z0-9_$]*', $reserved = array( - 'abstract','as','boolean','break','byte','case','catch','char','class', - 'const','continue','debugger','default','delete','do','double','else', - 'enum','export','extends','false','final','finally','float','for', - 'function','goto','if','implements','import','in','instanceof','int', - 'long','native','new','null','package','private','protected','public', - 'return','short','static','super','switch','synchronized','this', - 'throw','throws','transient','true','try','typeof','var','void', - 'while','with','yield','let','interface', + // Literals + 'true','false','null', + // ES6 + 'break','case','class','catch','const','continue','debugger','default','delete','do','else','export','extends','finally','for','function','if','import','in','instanceof','new','return','super','switch','this','throw','try','typeof','var','void','while','with','yield', + // Future + 'enum', + // Strict mode + 'implements','package','protected','static','let','interface','private','public', + // Module + 'await', + // Older standards + 'abstract','boolean','byte','char','double','final','float','goto','int','long','native','short','synchronized','throws','transient','volatile', ); @@ -330,14 +334,31 @@ class JSqueeze } else { - $a = $j && ' ' == $code[$j] ? $code[$j-1] : $code[$j]; - if (false !== strpos('-!%&;<=>~:^+|,(*?[{ ', $a) + $a = $j && (' ' == $code[$j] || "\x7F" == $code[$j]) ? $code[$j-1] : $code[$j]; + if (false !== strpos('-!%&;<=>~:^+|,()*?[{} ', $a) || (false !== strpos('oenfd', $a) && preg_match( - "'(? 1) + { + $a = 1; + $k = $j - (' ' == $code[$j] || "\x7F" == $code[$j]) - 1; + while ($k >= 0 && $a) + { + if ('(' === $code[$k]) --$a; + else if (')' === $code[$k]) ++$a; + --$k; + } + if (!preg_match("'(? 5) + if ($j > 3) { - ' ' == $code[$j] && --$j; + if (' ' == $code[$j] || "\x7F" == $code[$j]) --$j; $code[++$j] = - false !== strpos('kend', $code[$j-1]) + false !== strpos('kend+-', $code[$j-1]) && preg_match( - "'(?restoreCc($code, false); // Protect wanted spaces and remove unwanted ones + $code = strtr($code, "\x7F", ' '); $code = str_replace('- -', "-\x7F-", $code); $code = str_replace('+ +', "+\x7F+", $code); + $code = str_replace('get ', "get\x7F", $code); + $code = str_replace('set ', "set\x7F", $code); $code = preg_replace("'(\d)\s+\.\s*([a-zA-Z\$_[(])'", "$1\x7F.$2", $code); $code = preg_replace("# ([-!%&;<=>~:.^+|,()*?[\]{}/']+)#", '$1', $code); $code = preg_replace( "#([-!%&;<=>~:.^+|,()*?[\]{}/]+) #", '$1', $code); + $cc_on && $code = preg_replace_callback("'//[^\'].*?@#3'", function ($m) {return strtr($m[0], ' ', "\x7F");}, $code); // Replace new Array/Object by []/{} false !== strpos($code, 'new Array' ) && $code = preg_replace( "'new Array(?:\(\)|([;\])},:]))'", '[]$1', $code); @@ -490,18 +515,19 @@ class JSqueeze } $f = implode('', $f); - $cc_on && $f = str_replace('@#3', "\n", $f); + $cc_on && $f = str_replace('@#3', "\r", $f); // Fix "else ;" empty instructions $f = preg_replace("'(?used_tree =& $tree['used']; $tree['code'] = preg_replace_callback("#[.,{ ]?(?varRx}:?#", array(&$this, 'getNewName'), $tree['code']); - $this->specialVarRx && $tree['code'] = preg_replace_callback("#//''\"\"[0-9]+'#", array(&$this, 'renameInString'), $tree['code']); + + if ($this->specialVarRx && preg_match_all("#//''\"\"[0-9]+'#", $tree['code'], $b)) + { + foreach ($b[0] as $a) + { + $this->strings[$a] = preg_replace_callback( + "#[.,{]?(?specialVarRx}:?#", + array(&$this, 'getNewName'), + $this->strings[$a] + ); + } + } foreach ($tree['childs'] as $a => &$b) { @@ -908,18 +945,6 @@ class JSqueeze } } - protected function renameInString($a) - { - $b =& $this->strings[$a[0]]; - unset($this->strings[$a[0]]); - - return preg_replace_callback( - "#[.,{]?(?specialVarRx}:?#", - array(&$this, 'getNewName'), - $b - ); - } - protected function getNewName($m) { $m = $m[0]; diff --git a/sites/all/modules/contrib/advagg/advagg_mod/advagg_mod.admin.inc b/sites/all/modules/contrib/advagg/advagg_mod/advagg_mod.admin.inc index b9dc9295f..c223353d0 100644 --- a/sites/all/modules/contrib/advagg/advagg_mod/advagg_mod.admin.inc +++ b/sites/all/modules/contrib/advagg/advagg_mod/advagg_mod.admin.inc @@ -74,7 +74,7 @@ function advagg_mod_admin_settings_form() { '#default_value' => variable_get('advagg_mod_js_adjust_sort_browsers', ADVAGG_MOD_JS_ADJUST_SORT_BROWSERS), '#description' => t('This will group all browser conditional JavaScript to be in the lowest group of that conditional rule.'), ); - if (module_exists('googleanalytics') && variable_get('googleanalytics_cache', 0)) { + if (module_exists('googleanalytics')) { $form['js']['adjust_sort']['expert'] = array( '#type' => 'fieldset', '#title' => t('Experimental Settings'), @@ -86,6 +86,12 @@ function advagg_mod_admin_settings_form() { '#title' => t('Move Google Analytics analytics.js code from inline to be a file'), '#default_value' => variable_get('advagg_mod_ga_inline_to_file', ADVAGG_MOD_GA_INLINE_TO_FILE), ); + $form['js']['adjust_sort']['expert']['advagg_mod_prefetch'] = array( + '#type' => 'checkbox', + '#title' => t('Prefetch stats.g.doubleclick.net/robots.txt'), + '#default_value' => variable_get('advagg_mod_prefetch', ADVAGG_MOD_PREFETCH), + '#description' => t('Opens a connection to stats.g.doubleclick.net in order to speed up the round trip time.'), + ); } // Adjust javascript location and execution. @@ -326,6 +332,62 @@ function advagg_mod_admin_settings_form() { '@link' => 'http://stackoverflow.com/questions/19374843/css-delivery-optimization-how-to-defer-css-loading', )), ); + $form['css']['placement']['expert']['advagg_mod_css_defer_admin'] = array( + '#type' => 'checkbox', + '#title' => t('Use JS to load CSS in the admin theme'), + '#default_value' => variable_get('advagg_mod_css_defer_admin', ADVAGG_MOD_CSS_DEFER_ADMIN), + '#description' => t('This will optimize CSS delivery with JavaScript when viewing the admin theme'), + ); + + // Taken from block_admin_configure(). + $access = user_access('use PHP for settings'); + $css_defer_pages = variable_get('advagg_mod_css_defer_pages', ''); + $visibility_defer = variable_get('advagg_mod_css_defer_visibility', ADVAGG_MOD_VISIBILITY_LISTED); + $options = array( + ADVAGG_MOD_VISIBILITY_NOTLISTED => t('All pages except those listed'), + ADVAGG_MOD_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(ADVAGG_MOD_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'); + } + + if ($visibility_defer == ADVAGG_MOD_VISIBILITY_PHP && !$access) { + $form['css']['placement']['expert']['visibility_all'] = array( + '#type' => 'value', + '#value' => $visibility_defer, + ); + $form['css']['placement']['expert']['pages_all'] = array( + '#type' => 'value', + '#value' => $css_defer_pages, + ); + } + else { + $form['css']['placement']['expert']['advagg_mod_css_defer_visibility'] = array( + '#type' => 'radios', + '#title' => t('Defer CSS only on specific pages'), + '#options' => $options, + '#default_value' => $visibility_defer, + '#description' => t('Apply the above CSS setting only on specific pages. On other pages it will act as being Disabled.'), + ); + $form['css']['placement']['expert']['advagg_mod_css_defer_pages'] = array( + '#type' => 'textarea', + '#title' => '' . $title . '', + '#default_value' => $css_defer_pages, + '#description' => $description, + ); + } + $form['css']['placement']['expert']['advagg_mod_css_defer_js_code'] = array( '#type' => 'radios', '#title' => t('How to include the JS loading code'), @@ -344,10 +406,22 @@ function advagg_mod_admin_settings_form() { ), ), ); + $form['css']['placement']['expert']['advagg_mod_css_defer_rel_preload'] = array( + '#type' => 'checkbox', + '#title' => t('Use link rel="preload"'), + '#default_value' => variable_get('advagg_mod_css_defer_rel_preload', ADVAGG_MOD_CSS_DEFER_REL_PRELOAD), + '#description' => t('In supporting browsers, this markup will cause the browser to fetch the CSS file in an asynchronous, non-render-blocking manner, and once loaded, its onload event handler will change its rel property to "stylesheet" causing it to apply visibly in the page'), + '#states' => array( + 'disabled' => array( + ':input[name="advagg_mod_css_defer"]' => array('value' => '0'), + ), + ), + ); $pages_all = variable_get('advagg_mod_inline_pages', ''); $pages_css = variable_get('advagg_mod_inline_css_pages', ''); $pages_js = variable_get('advagg_mod_inline_js_pages', ''); + unset($options[ADVAGG_MOD_VISIBILITY_NOTLISTED]); $form['landing_page'] = array( '#type' => 'fieldset', '#title' => t('Inline CSS/JS on specific pages'), @@ -355,27 +429,6 @@ function advagg_mod_admin_settings_form() { '#collapsible' => TRUE, '#collapsed' => ($pages_all || $pages_css || $pages_js) ? FALSE : TRUE, ); - // Taken from block_admin_configure(). - $access = user_access('use PHP for settings'); - $options = array( - // ADVAGG_MOD_VISIBILITY_NOTLISTED => t('All pages except those listed'), - ADVAGG_MOD_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(ADVAGG_MOD_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'); - } - $visibility_all = variable_get('advagg_mod_inline_visibility', ADVAGG_MOD_VISIBILITY_LISTED); if ($visibility_all == ADVAGG_MOD_VISIBILITY_PHP && !$access) { $form['landing_page']['path']['visibility_all'] = array( @@ -515,10 +568,8 @@ function advagg_mod_admin_settings_form_validate($form, &$form_state) { * Clear out the advagg cache bin when the save configuration button is pressed. */ function advagg_mod_admin_settings_form_submit($form, &$form_state) { - $cache_bins = advagg_flush_caches(); - foreach ($cache_bins as $bin) { - cache_clear_all('*', $bin, TRUE); - } + // Clear caches. + advagg_cache_clear_admin_submit(); // If unified_multisite_dir has changed, flush menu router at the end of the // request. diff --git a/sites/all/modules/contrib/advagg/advagg_mod/advagg_mod.info b/sites/all/modules/contrib/advagg/advagg_mod/advagg_mod.info index cb00c0ef3..6538c8e85 100644 --- a/sites/all/modules/contrib/advagg/advagg_mod/advagg_mod.info +++ b/sites/all/modules/contrib/advagg/advagg_mod/advagg_mod.info @@ -6,9 +6,9 @@ dependencies[] = advagg configure = admin/config/development/performance/advagg/mod -; Information added by Drupal.org packaging script on 2015-07-18 -version = "7.x-2.14" +; Information added by Drupal.org packaging script on 2016-03-29 +version = "7.x-2.18" core = "7.x" project = "advagg" -datestamp = "1437181444" +datestamp = "1459291143" diff --git a/sites/all/modules/contrib/advagg/advagg_mod/advagg_mod.module b/sites/all/modules/contrib/advagg/advagg_mod/advagg_mod.module index ac0db165f..1477c50c8 100644 --- a/sites/all/modules/contrib/advagg/advagg_mod/advagg_mod.module +++ b/sites/all/modules/contrib/advagg/advagg_mod/advagg_mod.module @@ -19,7 +19,7 @@ define('ADVAGG_MOD_JS_PREPROCESS', FALSE); /** * Default value to add the defer tag to all script tags. */ -define('ADVAGG_MOD_JS_DEFER', FALSE); +define('ADVAGG_MOD_JS_DEFER', 0); /** * Default value to add use the async script shim for script tags. @@ -74,7 +74,12 @@ define('ADVAGG_MOD_CSS_ADJUST_SORT_BROWSERS', FALSE); /** * Default value to use JavaScript to defer CSS loading. */ -define('ADVAGG_MOD_CSS_DEFER', FALSE); +define('ADVAGG_MOD_CSS_DEFER', 0); + +/** + * Default value to use JavaScript to defer CSS loading in the admin theme. + */ +define('ADVAGG_MOD_CSS_DEFER_ADMIN', FALSE); /** * Default value to move CSS into drupal_add_css(). @@ -161,6 +166,16 @@ define('ADVAGG_MOD_JS_GET_EXTERNAL_DNS', FALSE); */ define('ADVAGG_MOD_JS_DEFER_JQUERY', FALSE); +/** + * Default value to use the prefetch tag for certain domains. + */ +define('ADVAGG_MOD_PREFETCH', FALSE); + +/** + * Default value of the inclusion method for the loadCSS code for rel=preload. + */ +define('ADVAGG_MOD_CSS_DEFER_REL_PRELOAD', FALSE); + // Core hook implementations. /** * Implements hook_module_implements_alter(). @@ -172,6 +187,11 @@ function advagg_mod_module_implements_alter(&$implementations, $hook) { unset($implementations['advagg_mod']); $implementations = array_merge($item, $implementations); } + + // Remove advagg_mod. Function gets called directly. + if ($hook === 'html_head_alter' && array_key_exists('advagg_mod', $implementations)) { + unset($implementations['advagg_mod']); + } } /** @@ -193,6 +213,21 @@ function advagg_mod_library_alter(&$javascript, $module) { * Implements hook_init(). */ function advagg_mod_init() { + // Adjust devel_shutdown callback. + if (variable_get('advagg_enabled', ADVAGG_ENABLED) + && (variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER) + || variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC) + )) { + $callbacks = &drupal_register_shutdown_function(); + foreach ($callbacks as $key => $values) { + if ($values['callback'] === 'devel_shutdown') { + $callbacks[$key]['callback'] = 'advagg_mod_devel_shutdown'; + break; + } + } + reset($callbacks); + } + // Return if unified_multisite_dir is not set. $dir = rtrim(variable_get('advagg_mod_unified_multisite_dir', ''), '/'); if (empty($dir) || !file_exists($dir) || !is_dir($dir)) { @@ -203,7 +238,8 @@ function advagg_mod_init() { $local_counter = advagg_get_global_counter(); if (!file_exists($counter_filename)) { module_load_include('inc', 'advagg', 'advagg.missing'); - advagg_save_data($counter_filename, $local_counter); + $errors = advagg_save_data($counter_filename, $local_counter); + return $errors; } else { $shared_counter = (int) file_get_contents($counter_filename); @@ -215,8 +251,8 @@ function advagg_mod_init() { elseif ($shared_counter < $local_counter) { // Local counter is higher, update saved file and return. module_load_include('inc', 'advagg', 'advagg.missing'); - advagg_save_data($counter_filename, $local_counter, TRUE); - return; + $errors = advagg_save_data($counter_filename, $local_counter, TRUE); + return $errors; } elseif ($shared_counter > $local_counter) { // Shared counter is higher, update local copy and return. @@ -289,73 +325,8 @@ function advagg_mod_js_alter(&$js) { // Add the defer or the async tag to JS. $jquery_defered = advagg_mod_js_async_defer($js); - - if ($jquery_defered) { - foreach ($js as &$values) { - // Skip if not inline. - if ($values['type'] !== 'inline') { - continue; - } - // Skip if advagg has already wrapped this inline code. - if (strpos($values['data'], 'advagg_mod_') !== FALSE) { - continue; - } - if (!empty($values['no_defer'])) { - continue; - } - - // Do not wrap inline js if it contains a named function definition. - $pattern = '/\\s*function\\s+((?:[a-z][a-z0-9_]*))\\s*\\(.*\\)\\s*\\{/smix'; - $match = preg_match($pattern, $values['data']); - if (!$match) { - // Defer the inline script by wrapping the code in setTimeout callback. - $matches[2] = $matches[0] = $values['data']; - $values['data'] = advagg_mod_wrap_inline_js($matches); - } - elseif (strpos($values['data'], 'jQuery.') !== FALSE) { - // Inline js has a named function that uses jQuery; - // do not defer jQuery.js - $no_jquery_defer = TRUE; - } - } - unset($values); - } - elseif (variable_get('advagg_mod_js_defer_inline_alter', ADVAGG_MOD_JS_DEFER_INLINE_ALTER)) { - foreach ($js as &$values) { - // Skip if not inline. - if ($values['type'] !== 'inline') { - continue; - } - // Skip if advagg has already wrapped this inline code. - if (strpos($values['data'], 'advagg_mod_') !== FALSE) { - continue; - } - if (!empty($values['no_defer'])) { - continue; - } - - // Do not wrap inline js if it contains a named function definition. - $pattern = '/\\s*function\\s+((?:[a-z][a-z0-9_]*))\\s*\\(.*\\)\\s*\\{/smix'; - $match = preg_match($pattern, $values['data']); - if (!$match) { - // Defer the inline script by wrapping the code in setTimeout callback. - $values['data'] = advagg_mod_defer_inline_js($values['data']); - } - } - unset($values); - } - if (!empty($no_jquery_defer)) { - foreach ($js as $name => &$values) { - // Skip if not a file or external. - if ($values['type'] !== 'file' && $values['type'] !== 'external') { - continue; - } - // Special handling for jQuery. - if (strpos($name, 'jquery.js') !== FALSE || strpos($name, 'jquery.min.js') !== FALSE) { - $values['defer'] = FALSE; - } - } - } + // Inline JS defer. + advagg_mod_inline_defer($js, $jquery_defered); // Move all async JS to the header. if (variable_get('advagg_mod_js_async_in_header', ADVAGG_MOD_JS_ASYNC_IN_HEADER)) { @@ -375,6 +346,29 @@ function advagg_mod_js_alter(&$js) { } unset($values); } + + advagg_mod_prefetch_link($js); +} + +/** + * Have the browser prefech this domain to open the connection. + * + * @param array $js + * JS array. + */ +function advagg_mod_prefetch_link(array &$js) { + if (variable_get('advagg_mod_prefetch', ADVAGG_MOD_PREFETCH)) { + foreach ($js as &$values) { + if (isset($values['dns_prefetch'])) { + foreach ($values['dns_prefetch'] as &$url) { + // Prefetch stats.g.doubleclick.net domain. + if (strpos($url, '//stats.g.doubleclick.net') !== FALSE) { + $url .= '#prefetch'; + } + } + } + } + } } /** @@ -601,8 +595,70 @@ function advagg_mod_advagg_modify_js_pre_render_alter(&$children, &$elements) { $onload_code[$values['#attributes']['src']] = $values['#attributes']['onload']; } } + $jquery_rev = strrev('/jquery.js'); + $jquery_min_rev = strrev('/jquery.min.js'); - foreach ($children as &$values) { + $ie_fixes = array(); + foreach ($elements['#groups'] as $group) { + if ( $group['type'] !== 'file' + || empty($group['defer']) + || empty($group['items']['files']) + ) { + continue; + } + + $found = FALSE; + foreach ($group['items']['files'] as $name => &$values) { + // Special handling for jQuery. + if ( stripos(strrev($name), $jquery_rev) === 0 + || stripos(strrev($name), $jquery_min_rev) === 0 + ) { + $found = TRUE; + } + } + if ($found) { + $ie_fixes[] = basename($group['data']); + } + } + + foreach ($children as $key => &$values) { + if ( !empty($values['#attributes']['src']) + && isset($values['#attributes']['defer']) + && empty($values['#browsers']) + ) { + $ie_key = array_search(basename($values['#attributes']['src']), $ie_fixes); + if ($ie_key !== FALSE) { + unset($ie_fixes[$ie_key]); + // Not IE supports defer. + $values['#browsers'] = array( + 'IE' => FALSE, + '!IE' => TRUE, + ); + + // IE10+ supports defer. + $copy = $values; + $copy['#browsers'] = array( + 'IE' => 'gt IE 9', + '!IE' => FALSE, + ); + $copy['#attributes']['src'] .= '#ie10+'; + array_splice($children, $key, 0, array($copy)); + + // IE9- does not support defer. + $copy = $values; + $copy['#browsers'] = array( + 'IE' => 'lte IE 9', + '!IE' => FALSE, + ); + unset($copy['defer']); + unset($copy['#attributes']['defer']); + $copy['#attributes']['src'] .= '#ie9-'; + array_splice($children, $key, 0, array($copy)); + } + } + } + + foreach ($children as $key => &$values) { // Core's Drupal.settings. Put inside wrapper if there is an onload call // for init_drupal_core_settings. Have to do this here because the // settings needed to be rendered. @@ -616,7 +672,7 @@ function advagg_mod_advagg_modify_js_pre_render_alter(&$children, &$elements) { } } if ($found) { - $values['#value'] = "function init_drupal_core_settings() {" . $values['#value'] . "\nif(jQuery.isFunction(jQuery.holdReady)){jQuery.holdReady(false);}}"; + $values['#value'] = "function init_drupal_core_settings() {" . $values['#value'] . "\nif(jQuery.isFunction(jQuery.holdReady)){jQuery.holdReady(false);}} if(window.jQuery && window.Drupal){init_drupal_core_settings();}"; } } } @@ -658,29 +714,42 @@ function advagg_mod_advagg_modify_css_pre_render_alter(&$children, &$elements) { return; } + // Return early if we're in a page that is not specified in the settings for + // specific pages. + if (!advagg_mod_css_defer_page()) { + return; + } + // Return early if this setting is disabled. $css_defer = variable_get('advagg_mod_css_defer', ADVAGG_MOD_CSS_DEFER); if (empty($css_defer)) { return; } + + // Return early if we're in the admin theme and this setting is disabled. + $css_defer_admin = variable_get('advagg_mod_css_defer_admin', ADVAGG_MOD_CSS_DEFER_ADMIN); + if (empty($css_defer_admin) && path_is_admin(current_path())) { + return; + } + $css_defer_js_code = variable_get('advagg_mod_css_defer_js_code', ADVAGG_MOD_CSS_DEFER_JS_CODE); - // Make advagg_mod_loadStyleSheet() available. + // Make loadCSS() available. $type = 'external'; // https://github.com/filamentgroup/loadCSS. - $data = '//cdn.rawgit.com/filamentgroup/loadCSS/master/loadCSS.js'; + $data = '//cdn.rawgit.com/filamentgroup/loadCSS/master/src/loadCSS.js'; $min = ''; if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 0) { $min = '.min'; } if ($css_defer_js_code == 2) { $type = 'file'; - $data = drupal_get_path('module', 'advagg_mod') . "/loadCSS$min.js"; + $data = drupal_get_path('module', 'advagg_mod') . "/loadCSS{$min}.js"; } if ($css_defer_js_code == 0) { $type = 'inline'; - $data = '//[c]2014 @scottjehl, Filament Group, Inc. Licensed MIT. -function loadCSS(a,b,c,d){"use strict";var e=window.document.createElement("link"),f=b||window.document.getElementsByTagName("script")[0],g=window.document.styleSheets;return e.rel="stylesheet",e.href=a,e.media="only x",d&&(e.onload=d),f.parentNode.insertBefore(e,f),e.onloadcssdefined=function(b){for(var c,d=0;d-1&&(c=!0);c?b():setTimeout(function(){e.onloadcssdefined(b)})},e.onloadcssdefined(function(){e.media=c||"all"}),e}'; + $data = '/*! loadCSS: load a CSS file asynchronously. [c]2016 @scottjehl, Filament Group, Inc. Licensed MIT */ +!function(e){"use strict";var t=function(t,n,i){var o,a=e.document,d=a.createElement("link"),r=i||"all";if(n)o=n;else{var l=(a.body||a.getElementsByTagName("head")[0]).childNodes;o=l[l.length-1]}var s=a.styleSheets;d.rel="stylesheet",d.href=t,d.media="only x",o.parentNode.insertBefore(d,n?o:o.nextSibling);var f=function(e){for(var t=d.href,n=s.length;n--;)if(s[n].href===t)return e();setTimeout(function(){f(e)})};return d.addEventListener&&d.addEventListener("load",function(){this.media=r}),d.onloadcssdefined=f,f(function(){d.media!==r&&(d.media=r)}),d};"undefined"!=typeof exports?exports.loadCSS=t:e.loadCSS=t}("undefined"!=typeof global?global:this);'; } $options = array( 'type' => $type, @@ -702,6 +771,44 @@ function loadCSS(a,b,c,d){"use strict";var e=window.document.createElement("link drupal_add_js($data, $options); $added['inline'] = TRUE; } + if (variable_get('advagg_mod_css_defer_rel_preload', ADVAGG_MOD_CSS_DEFER_REL_PRELOAD)) { + // Make cssrelpreload available. + $type = 'external'; + // https://github.com/filamentgroup/loadCSS. + $data = '//cdn.rawgit.com/filamentgroup/loadCSS/master/src/cssrelpreload.js'; + $min = ''; + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 0) { + $min = '.min'; + } + if ($css_defer_js_code == 2) { + $type = 'file'; + $data = drupal_get_path('module', 'advagg_mod') . "/cssrelpreload{$min}.js"; + } + if ($css_defer_js_code == 0) { + $type = 'inline'; + $data = '/*! CSS rel=preload polyfill. Depends on loadCSS function. [c]2016 @scottjehl, Filament Group, Inc. Licensed MIT */ +!function(t){if(t.loadCSS){var e=loadCSS.relpreload={};if(e.support=function(){try{return t.document.createElement("link").relList.supports("preload")}catch(e){return!1}},e.poly=function(){for(var e=t.document.getElementsByTagName("link"),n=0;n $type, + 'scope' => $css_defer >= 7 ? 'footer' : 'header', + 'scope_lock' => TRUE, + 'every_page' => TRUE, + 'group' => $css_defer == 1 ? JS_LIBRARY - 1 : JS_LIBRARY, + 'weight' => $css_defer == 1 ? -50000 : 0, + 'movable' => $css_defer == 1 ? FALSE : TRUE, + ); + if ($type !== 'inline') { + $options['async'] = TRUE; + } + else { + $options['no_defer'] = TRUE; + } + if (!isset($added['inline'])) { + drupal_add_js($data, $options); + $added['inline'] = TRUE; + } + } // Wrap CSS in noscript tags. $options = array( @@ -831,8 +938,18 @@ function advagg_mod_js_no_ajaxpagestate(array &$scripts) { return; } - // No ajax.js but there is a settings array. - if (!isset($scripts['#items']['misc/ajax.js']) && isset($scripts['#items']['settings']['data'])) { + // Search for the ajax file in the #items array. + $ajax_found = FALSE; + if (isset($scripts['#items']) && is_array($scripts['#items'])) { + foreach ($scripts['#items'] as $key => $values) { + if (strpos($key, 'misc/ajax.js') !== FALSE || strpos($key, 'misc/ajax.min.js')) { + $ajax_found = TRUE; + break; + } + } + } + // The ajax.js file was not found and there is a settings array. + if (!$ajax_found && isset($scripts['#items']['settings']['data'])) { foreach ($scripts['#items']['settings']['data'] as $delta => $setting) { if (array_key_exists('ajaxPageState', $setting)) { // Remove js files. @@ -861,10 +978,13 @@ function advagg_mod_js_no_ajaxpagestate(array &$scripts) { * Controls inline wrapping and defer. Controls no async/defer file list. * Controls files that say in the header. * + * @param array $js + * The JS array. + * * @return array * A multidimensional array. */ -function advagg_mod_get_lists() { +function advagg_mod_get_lists(array $js = array()) { $lists = &drupal_static(__FUNCTION__); if (!isset($lists)) { // Do not move to footer file list. @@ -895,10 +1015,16 @@ function advagg_mod_get_lists() { // Get inline wrap js skip list string and convert it to an array. $inline_js_wrap_skip_list = array_filter(array_map('trim', explode("\n", variable_get('advagg_mod_wrap_inline_js_skip_list', ADVAGG_MOD_WRAP_INLINE_JS_SKIP_LIST)))); + if (module_exists('devel')) { + $inline_js_wrap_skip_list[] = 'function krumo('; + } // Get inline defer js skip list string and convert it to an array. $inline_js_defer_skip_list = array_filter(array_map('trim', explode("\n", variable_get('advagg_mod_defer_inline_js_skip_list', ADVAGG_MOD_DEFER_INLINE_JS_SKIP_LIST)))); $inline_js_defer_skip_list[] = 'loadCSS('; + if (module_exists('googleanalytics')) { + $inline_js_defer_skip_list[] = '_gaq.push(["_'; + } // If there is a fast clicker, ajax links might not work if ajax.js is // loaded in the footer. @@ -955,6 +1081,10 @@ function advagg_mod_get_lists() { $inline_js_defer_skip_list[] = 'GA_googleFillSlot('; } + $move_js_to_footer = variable_get('advagg_mod_js_footer', ADVAGG_MOD_JS_FOOTER); + $defer_setting = variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER); + $async_setting = variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC); + // Allow other modules to add/edit the above lists. // Call hook_advagg_mod_get_lists_alter(). $lists = array( @@ -965,8 +1095,11 @@ function advagg_mod_get_lists() { $inline_js_wrap_skip_list, $inline_js_defer_skip_list, $all_in_footer_list, + $move_js_to_footer, + $defer_setting, + $async_setting, ); - drupal_alter('advagg_mod_get_lists', $lists); + drupal_alter('advagg_mod_get_lists', $lists, $js); } return $lists; } @@ -984,7 +1117,10 @@ function advagg_mod_js_move_to_footer(array &$js) { return; } - list($header_file_list, $header_inline_list, , , , , $all_in_footer_list) = advagg_mod_get_lists(); + list($header_file_list, $header_inline_list, , , , , $all_in_footer_list, $move_js_to_footer) = advagg_mod_get_lists($js); + if (empty($move_js_to_footer)) { + return; + } // Process all in footer list. if ($move_js_to_footer == 3 && !empty($all_in_footer_list)) { @@ -1067,7 +1203,10 @@ function advagg_mod_js_async_defer(array &$js) { return $jquery_defered; } - list(, , $no_async_defer_list, $inline_wrapper_list) = advagg_mod_get_lists(); + list(, , $no_async_defer_list, $inline_wrapper_list, , , , , $defer_setting, $async_setting) = advagg_mod_get_lists($js); + if (!$defer_setting && !$async_setting) { + return $jquery_defered; + } // Disable this section of code for now; the on error attribute only works // with async safe JS. @@ -1086,7 +1225,9 @@ function advagg_mod_js_async_defer(array &$js) { if ($values['type'] !== 'inline') { continue; } - if (stripos($values['data'], 'window.jQuery.ui') !== FALSE) { + if ( stripos($values['data'], 'window.jQuery.ui') !== FALSE + && stripos($values['data'], 'document.write(" &$values) { + // Skip if not a file or external. + if ($values['type'] !== 'file' && $values['type'] !== 'external') { + continue; + } + // Special handling for jQuery. + if ( stripos(strrev($name), $jquery_rev) === 0 + || stripos(strrev($name), $jquery_min_rev) === 0 + ) { + $values['defer'] = FALSE; + } + } + } +} + /** * Callback for pre_render to inline all JavaScript on this page. * @@ -1390,7 +1624,12 @@ function advagg_mod_xpath_script_external_dns($html) { * Inline javascript code wrapped up in a loader. */ function advagg_mod_defer_inline_js($input) { - if (variable_get('advagg_mod_js_defer_jquery', ADVAGG_MOD_JS_DEFER_JQUERY)) { + if ( variable_get('advagg_mod_js_defer_jquery', ADVAGG_MOD_JS_DEFER_JQUERY) + && ( stripos($input, 'jQuery') !== FALSE + || stripos($input, '$(') !== FALSE + || stripos($input, 'Drupal.') !== FALSE + ) + ) { $matches[2] = $matches[0] = $input; return advagg_mod_wrap_inline_js($matches); } @@ -1766,6 +2005,18 @@ function advagg_mod_inline_page_js() { return advagg_mod_match_path($pages, $visibility); } +/** + * Returns TRUE if this page should have critical CSS inlined. + * + * @return bool + * TRUE or FALSE. + */ +function advagg_mod_css_defer_page() { + $visibility = variable_get('advagg_mod_css_defer_visibility', ADVAGG_MOD_VISIBILITY_NOTLISTED); + $pages = variable_get('advagg_mod_css_defer_pages', ''); + return advagg_mod_match_path($pages, $visibility); +} + /** * Transforms all JS files into inline JS. * @@ -1965,43 +2216,81 @@ function advagg_mod_ga_inline_to_file(array &$js) { // Get inline GA js and put it inside of an aggregrate. $ga_script = ''; $debug = variable_get('googleanalytics_debug', 0); - $library_tracker_url = '//www.google-analytics.com/' . ($debug ? 'analytics_debug.js' : 'analytics.js'); - $library_cache_url = 'http:' . $library_tracker_url; + $api = googleanalytics_api(); + if ($api['api'] === 'analytics.js') { + $library_tracker_url = '//www.google-analytics.com/' . ($debug ? 'analytics_debug.js' : 'analytics.js'); + $library_cache_url = 'http:' . $library_tracker_url; + } + else { + // Which version of the tracking library should be used? + if ($trackdoubleclick = variable_get('googleanalytics_trackdoubleclick', FALSE)) { + $library_tracker_url = 'stats.g.doubleclick.net/dc.js'; + $library_cache_url = 'http://' . $library_tracker_url; + } + else { + $library_tracker_url = '.google-analytics.com/ga.js'; + $library_cache_url = 'http://www' . $library_tracker_url; + } + } $ga_script = _googleanalytics_cache($library_cache_url); if (variable_get('googleanalytics_cache', 0) && $ga_script) { - // A dummy query-string is added to filenames, to gain control over - // browser-caching. The string changes on every update or full cache - // flush, forcing browsers to load a new copy of the files, as the - // URL changed. - $ga_script_len = strlen('"' . $ga_script . '?' . variable_get('css_js_query_string', '0') . '"'); - $mod_base_url = substr($GLOBALS['base_root'] . $GLOBALS['base_path'], strpos($GLOBALS['base_root'] . $GLOBALS['base_path'], '//') + 2); $mod_base_url_len = strlen($mod_base_url); $ga_script = substr($ga_script, stripos($ga_script, $mod_base_url) + $mod_base_url_len); } + else { + $ga_script = $library_cache_url; + if ($api['api'] === 'ga.js' && $GLOBALS['is_https']) { + if (!empty($trackdoubleclick)) { + $ga_script = str_replace('http://', 'https://', $ga_script); + } + else { + $ga_script = str_replace('http://www', 'https://ssl', $ga_script); + } + } + } - if (!empty($ga_script) && !empty($ga_script_len)) { + if (!empty($ga_script)) { foreach ($js as $key => $value) { // Skip if not inline. if ($value['type'] !== 'inline') { continue; } - // Skip if it doesn't start with the GoogleAnalytics inline loader string. - if (strpos($value['data'], '(function(i,s,o,g,r,a,m){i["GoogleAnalyticsObject"]=r;i[r]=i[r]||function(){(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)})(window,document,"script",') !== 0) { - continue; + $add_ga = FALSE; + // GoogleAnalytics 2.x inline loader string. + if ($api['api'] === 'analytics.js') { + $start = strpos($value['data'], '(function(i,s,o,g,r,a,m){i["GoogleAnalyticsObject"]=r;i[r]=i[r]||function(){(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();'); + $end = strpos($value['data'], '})(window,document,"script",'); + if ($start === 0) { + // Strip loader string. + $js[$key]['data'] = substr($value['data'], 0, $start + 133) . substr($value['data'], $end); + $js[$key]['data'] = advagg_mod_defer_inline_js($js[$key]['data']); + $add_ga = TRUE; + } + } + // GoogleAnalytics 1.x inline loader string. + if ($api['api'] === 'ga.js') { + $start = strpos($value['data'], '(function() {var ga = document.createElement("script");ga.type = "text/javascript";ga.async = true;ga.src ='); + $end = strpos($value['data'], '";var s = document.getElementsByTagName("script")[0];s.parentNode.insertBefore(ga, s);})();'); + if ($start !== FALSE && $end !== FALSE) { + // Strip loader string. + $js[$key]['data'] = substr($value['data'], 0, $start) . substr($value['data'], $end + 91); + $js[$key]['no_defer'] = TRUE; + $add_ga = TRUE; + } + } + + if ($add_ga) { + // Add GA analytics.js file to the $js array. + $js[$ga_script] = array( + 'data' => $ga_script, + 'type' => 'file', + 'async' => TRUE, + 'defer' => TRUE, + ); + $js[$ga_script] += $value; + break; } - // Strip loader string. - $matches[2] = $matches[0] = substr($value['data'], 261 + $ga_script_len + 7); - $js[$key]['data'] = advagg_mod_wrap_inline_js($matches, "window.ga"); - - // Add GA analytics.js file to the $js array. - $js[$ga_script] = array( - 'data' => $ga_script, - 'type' => 'file', - // 'async' => TRUE, - ); - $js[$ga_script] += $value; - break; } } } @@ -2211,3 +2500,156 @@ function advagg_mod_magic_form_validate($form, &$form_state) { $form_state['values']['magic_library_head'] = 0; $form_state['values']['magic_experimental_js'] = 0; } + + +/** + * Runs on shutdown to clean up and display developer information. + * + * This function is registered by devel_boot() as a shutdown function. + * The bulk of the work is done in devel_shutdown_real(). + */ +function advagg_mod_devel_shutdown() { + // Register the real shutdown function so it runs after other shutdown + // functions. + drupal_register_shutdown_function('advagg_mod_devel_shutdown_real'); +} + +/** + * Runs on shutdown to display developer information in the footer. + * + * This function is registered by devel_shutdown() as a shutdown function. + */ +function advagg_mod_devel_shutdown_real() { + global $user; + $output = ''; + + // Set $GLOBALS['devel_shutdown'] = FALSE in order to suppress the + // devel footer for a page. Not necessary if your page outputs any + // of the Content-type http headers tested below (e.g. text/xml, + // text/javascript, etc). This is is advised where applicable. + if (!devel_silent() && !isset($GLOBALS['devel_shutdown']) && !isset($GLOBALS['devel_redirecting'])) { + // Try not to break non html pages. + if (function_exists('drupal_get_http_header')) { + $header = drupal_get_http_header('content-type'); + if ($header) { + $formats = array( + 'xml', + 'javascript', + 'json', + 'plain', + 'image', + 'application', + 'csv', + 'x-comma-separated-values', + ); + foreach ($formats as $format) { + if (strstr($header, $format)) { + return; + } + } + } + } + + if (isset($user) && user_access('access devel information')) { + $queries = (devel_query_enabled() ? Database::getLog('devel', 'default') : NULL); + if (!empty($queries)) { + // Remove caller args to avoid recursion. + foreach ($queries as &$query) { + unset($query['caller']['args']); + } + } + $output .= devel_shutdown_summary($queries); + $output .= advagg_mod_devel_shutdown_query($queries); + } + + if ($output) { + // TODO: gzip this text if we are sending a gzip page. + // See drupal_page_header(). + // For some reason, this is not actually printing for cached pages even + // though it gets executed and $output looks good. + print $output; + } + } +} + +/** + * Returns the rendered query log. + */ +function advagg_mod_devel_shutdown_query($queries) { + if (!empty($queries)) { + if (function_exists('theme_get_registry') && theme_get_registry()) { + // Safe to call theme('table). + list($counts) = devel_query_summary($queries); + $output = devel_query_table($queries, $counts); + + // Save all queries to a file in temp dir. Retrieved via AJAX. + advagg_mod_devel_query_put_contents($queries); + } + else { + $output = '
' . dprint_r($queries, TRUE); + } + return $output; + } +} + +/** + * Writes the variables information to a file. + * + * It will be retrieved on demand via AJAX. + */ +function advagg_mod_devel_query_put_contents($queries) { + $request_id = mt_rand(1, 1000000); + $path = "temporary://devel_querylog"; + + // Create the devel_querylog within the temp folder, if needed. + file_prepare_directory($path, FILE_CREATE_DIRECTORY); + + // Occasionally wipe the querylog dir so that files don't accumulate. + if (mt_rand(1, 1000) == 401) { + devel_empty_dir($path); + } + + $path .= "/$request_id.txt"; + $path = file_stream_wrapper_uri_normalize($path); + // Save queries as a json array. Suppress errors due to recursion. + $options = JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT; + if (version_compare(PHP_VERSION, '5.5.0', '>=')) { + $options |= JSON_PARTIAL_OUTPUT_ON_ERROR; + } + if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + $options |= JSON_PRETTY_PRINT; + } + + // Prevent empty json data due to recursion. + $depth = 32; + $json_data = FALSE; + while (empty($json_data) && $depth > 0) { + $json_data = @json_encode($queries, $options, $depth); + $depth--; + } + + file_put_contents($path, $json_data); + $settings['devel'] = array( + // A random string that is sent to the browser. + // It enables the AJAX to retrieve queries from this request. + 'request_id' => $request_id, + ); + $inline = 'jQuery.extend(Drupal.settings, ' . json_encode($settings) . ');'; + if ( variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER) + || variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC) + ) { + $matches[2] = $matches[0] = $inline; + $inline = advagg_mod_wrap_inline_js($matches); + } + $options = array( + 'type' => 'inline', + ); + $options += drupal_js_defaults($inline); + $scripts_array = array( + '#type' => 'scripts', + '#items' => array($options), + ); + $scripts = drupal_render($scripts_array); + + print $scripts; +} diff --git a/sites/all/modules/contrib/advagg/advagg_mod/advagg_mod_css_defer.js b/sites/all/modules/contrib/advagg/advagg_mod/advagg_mod_css_defer.js index aedbf7f21..d58d559cf 100644 --- a/sites/all/modules/contrib/advagg/advagg_mod/advagg_mod_css_defer.js +++ b/sites/all/modules/contrib/advagg/advagg_mod/advagg_mod_css_defer.js @@ -6,7 +6,7 @@ /** * Given a css file, load it using JavaScript. * - * @param string src + * @param {string} src * URL of the css file to load. */ function advagg_mod_loadStyleSheet(src) { diff --git a/sites/all/modules/contrib/advagg/advagg_mod/cssrelpreload.js b/sites/all/modules/contrib/advagg/advagg_mod/cssrelpreload.js new file mode 100644 index 000000000..1a264a0bf --- /dev/null +++ b/sites/all/modules/contrib/advagg/advagg_mod/cssrelpreload.js @@ -0,0 +1,50 @@ +/** + * @file + * Used to load CSS via JS so css doesn't block the browser. + */ + +/* @codingStandardsIgnoreFile */ + +/*! CSS rel=preload polyfill. Depends on loadCSS function. [c]2016 @scottjehl, Filament Group, Inc. Licensed MIT */ +(function( w ){ + // rel=preload support test + if( !w.loadCSS ){ + return; + } + var rp = loadCSS.relpreload = {}; + rp.support = function(){ + try { + return w.document.createElement( "link" ).relList.supports( "preload" ); + } catch (e) { + return false; + } + }; + + // loop preload links and fetch using loadCSS + rp.poly = function(){ + var links = w.document.getElementsByTagName( "link" ); + for( var i = 0; i < links.length; i++ ){ + var link = links[ i ]; + if( link.rel === "preload" && link.getAttribute( "as" ) === "style" ){ + w.loadCSS( link.href, link ); + link.rel = null; + } + } + }; + + // if link[rel=preload] is not supported, we must fetch the CSS manually using loadCSS + if( !rp.support() ){ + rp.poly(); + var run = w.setInterval( rp.poly, 300 ); + if( w.addEventListener ){ + w.addEventListener( "load", function(){ + w.clearInterval( run ); + } ); + } + if( w.attachEvent ){ + w.attachEvent( "onload", function(){ + w.clearInterval( run ); + } ) + } + } +}( this )); diff --git a/sites/all/modules/contrib/advagg/advagg_mod/cssrelpreload.min.js b/sites/all/modules/contrib/advagg/advagg_mod/cssrelpreload.min.js new file mode 100644 index 000000000..8e1a98d64 --- /dev/null +++ b/sites/all/modules/contrib/advagg/advagg_mod/cssrelpreload.min.js @@ -0,0 +1,2 @@ +/*! CSS rel=preload polyfill. Depends on loadCSS function. [c]2016 @scottjehl, Filament Group, Inc. Licensed MIT */ +!function(t){if(t.loadCSS){var e=loadCSS.relpreload={};if(e.support=function(){try{return t.document.createElement("link").relList.supports("preload")}catch(e){return!1}},e.poly=function(){for(var e=t.document.getElementsByTagName("link"),n=0;n - // By default, `before` uses the first